diff --git a/.vscode/launch.json b/.vscode/launch.json
index 8bb6590..fb4651f 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -22,7 +22,18 @@
"port": 6011,
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/server/out/**/*.js"]
- }
+ },
+ {
+ "name": "Extension Tests",
+ "type": "extensionHost",
+ "request": "launch",
+ "runtimeExecutable": "${execPath}",
+ "args": [
+ "--extensionDevelopmentPath=${workspaceFolder}",
+ "--extensionTestsPath=${workspaceFolder}/client/out/test/index"
+ ],
+ "outFiles": ["${workspaceFolder}client/out/test/**/*.js"]
+ }
],
"compounds": [
{
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0141ca3..ce8044a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.1.6 (May 06, 2020)
+- Performance update
+
+## 0.1.5 (May 04, 2020)
+- Node package issue
+
## 0.1.4 (May 02, 2020)
- Opening links with capital letters issue fixed
- Duplicate label statement warning
diff --git a/README.md b/README.md
index 5eb9b07..067f49b 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
+![release](https://img.shields.io/github/v/release/iSorp/macro-executor)
![maintained](https://img.shields.io/maintenance/yes/2020.svg)
[![open issues](https://img.shields.io/github/issues/iSorp/macro-executor.svg?)](https://github.com/iSorp/macro-executor/issues)
[![license](https://img.shields.io/github/license/iSorp/macro-executor)](https://opensource.org/licenses/MIT)
@@ -38,8 +39,13 @@ Fanuc Macro Executor syntax highlighting, validating and project building
![Definition](./resources/codelens.png)
## Required file extension
-* Macro files `.src`
+* Macro files`.src`
* Include files `.def`
+* Link files `.lnk`
+
+## Coding conventions
+* Uppercase for constants: `@MY_CONSTANT` 100
+* Space between statements: `N9000 G01 X1`
## Default Commands
@@ -95,13 +101,13 @@ project
│ file2.src
│
└───def
-│ file1.def
-│ file1.def
+│ file1.def
+│ file1.def
│
└───lnk
file1.lnk
file1.lnk
- F30iA_01.MEX
+ F30iA_01.MEX
```
diff --git a/TODO b/TODO
index 52dba10..c39fae8 100644
--- a/TODO
+++ b/TODO
@@ -2,3 +2,5 @@
- Symbol search for NC Statemets
- Parsing of .lnk files (read declarated "FILE=" properties validate only these files)
- Improve bild system (read properties of .lnk files)
+- Improve lint: constant checking,..
+- GOTO -> goto implementation
diff --git a/client/package.json b/client/package.json
index 070e749..bef7573 100644
--- a/client/package.json
+++ b/client/package.json
@@ -3,7 +3,7 @@
"description": "Fanuc Macro-Executor Programming Language",
"author": "iSorp",
"license": "MIT",
- "version": "0.0.1",
+ "version": "0.1.0",
"publisher": "iSorp",
"repository": {
"type": "git",
diff --git a/client/src/extension.ts b/client/src/extension.ts
index 9e09995..a2cafd5 100644
--- a/client/src/extension.ts
+++ b/client/src/extension.ts
@@ -2,7 +2,7 @@ import * as path from 'path';
import { ExtensionContext, workspace, commands, window, Selection } from 'vscode';
import {
LanguageClient, LanguageClientOptions,
- ServerOptions, TransportKind, RevealOutputChannelOn, Position,
+ ServerOptions, TransportKind, RevealOutputChannelOn,
} from 'vscode-languageclient';
import registerCommands from './common/commands';
@@ -45,7 +45,7 @@ export function activate(context: ExtensionContext) {
synchronize: {
// Notify the server about file changes
- fileEvents: workspace.createFileSystemWatcher('**/*.{src,def}')
+ fileEvents: workspace.createFileSystemWatcher('**/*.{src,def,lnk}')
},
diagnosticCollectionName: 'macro',
progressOnInitialization: true,
diff --git a/client/src/test/completion.test.ts b/client/src/test/completion.test.ts
deleted file mode 100644
index f355078..0000000
--- a/client/src/test/completion.test.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/* --------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- * ------------------------------------------------------------------------------------------ */
-
-import * as vscode from 'vscode';
-import * as assert from 'assert';
-import { getDocUri, activate } from './helper';
-
-suite('Should do completion', () => {
- const docUri = getDocUri('completion.txt');
-
- test('Completes JS/TS in txt file', async () => {
- await testCompletion(docUri, new vscode.Position(0, 0), {
- items: [
- { label: 'JavaScript', kind: vscode.CompletionItemKind.Text },
- { label: 'TypeScript', kind: vscode.CompletionItemKind.Text }
- ]
- });
- });
-});
-
-async function testCompletion(
- docUri: vscode.Uri,
- position: vscode.Position,
- expectedCompletionList: vscode.CompletionList
-) {
- await activate(docUri);
-
- // Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion
- const actualCompletionList = (await vscode.commands.executeCommand(
- 'vscode.executeCompletionItemProvider',
- docUri,
- position
- )) as vscode.CompletionList;
-
- assert.ok(actualCompletionList.items.length >= 2);
- expectedCompletionList.items.forEach((expectedItem, i) => {
- const actualItem = actualCompletionList.items[i];
- assert.equal(actualItem.label, expectedItem.label);
- assert.equal(actualItem.kind, expectedItem.kind);
- });
-}
diff --git a/client/src/test/diagnostics.test.ts b/client/src/test/diagnostics.test.ts
index 1aa8a36..ec5b0a2 100644
--- a/client/src/test/diagnostics.test.ts
+++ b/client/src/test/diagnostics.test.ts
@@ -8,7 +8,7 @@ import * as assert from 'assert';
import { getDocUri, activate } from './helper';
suite('Should get diagnostics', () => {
- const docUri = getDocUri('diagnostics.txt');
+ const docUri = getDocUri('src/test.src');
test('Diagnoses uppercase texts', async () => {
await testDiagnostics(docUri, [
diff --git a/client/src/test/helper.ts b/client/src/test/helper.ts
index 6e6724d..fa991ec 100644
--- a/client/src/test/helper.ts
+++ b/client/src/test/helper.ts
@@ -16,7 +16,7 @@ export let platformEol: string;
*/
export async function activate(docUri: vscode.Uri) {
// The extensionId is `publisher.name` from package.json
- const ext = vscode.extensions.getExtension('vscode-samples.lsp-sample')!;
+ const ext = vscode.extensions.getExtension('iSorp.macro-executor')!;
await ext.activate();
try {
doc = await vscode.workspace.openTextDocument(docUri);
@@ -32,7 +32,7 @@ async function sleep(ms: number) {
}
export const getDocPath = (p: string) => {
- return path.resolve(__dirname, '../../testFixture', p);
+ return path.resolve(__dirname, '../../testSource', p);
};
export const getDocUri = (p: string) => {
return vscode.Uri.file(getDocPath(p));
diff --git a/client/testSource/src/Test.src b/client/src/test/testSource/src/test.src
similarity index 53%
rename from client/testSource/src/Test.src
rename to client/src/test/testSource/src/test.src
index 6509998..82e8a39 100644
--- a/client/testSource/src/Test.src
+++ b/client/src/test/testSource/src/test.src
@@ -1,13 +1,24 @@
-$INCLUDE def/Test.def
+@CONST 1000
+@adr 1000
+@var #1000
->Label 10
-@functions 1
-@operators 2
-@conditionals 3
-@statements 4
+@CALL M98P
+@RETURN M99
-O functions
- #c1=ATAN[#j]/[#k]
+>LOOP_I 10
+
+@FUNCTIONS 1
+@OPERATORS 2
+@CONDITIONALS 3
+@STATEMENTS 4
+
+@i 200
+@k 200
+@j 200
+
+
+O FUNCTIONS
+ #i=ATAN[#j]/[#k]
#i=ABS[#j]
#i=BIN[#j]
#i=BCD[#j]
@@ -33,7 +44,7 @@ O functions
ENDIF
-O operators
+O OPERATORS
#i=#j
#i=#j+#k
#i=#j-#k
@@ -45,22 +56,24 @@ O operators
#i=#j MOD #k
-O conditionals
- Label
+O CONDITIONALS
+ >L_Label 10
+
+ L_Label
- GOTO Label
+ GOTO L_Label
- IF [#VAR] THEN #VAR = 1
- ELSE #VAR = 1
+ IF [#var] THEN #var = 1
+ ELSE #var = 1
- IF [#VAR] THEN
- #VAR = 1
- ELSE #VAR = 1
+ IF [#var] THEN
+ #var = 1
+ ELSE #var = 1
- IF [#VAR] THEN #VAR = 1
+ IF [#var] THEN #var = 1
ELSE
- #VAR = 1
+ #var = 1
ENDIF
IF [#k] THEN
@@ -69,13 +82,13 @@ O conditionals
ENDIF
ENDIF
- WHILE [#VAR LT CONST] DO LOOP_I
+ WHILE [#var LT CONST] DO LOOP_I
GOTO L_Label
END LOOP_I
-O statements
- N100 G01 G4.1 G[1] G#VAR X1 Y-[#1+1] F360.
- N100 G01 X-#1 F360.1
+O STATEMENTS
+ N100 G01 G4.1 G[1] G#var X1 Y-[#1+1] F360.
+ N110 G01 X-#1 F360.1
RETURN
\ No newline at end of file
diff --git a/client/testSource/def/Test.def b/client/testSource/def/Test.def
deleted file mode 100644
index caceee5..0000000
--- a/client/testSource/def/Test.def
+++ /dev/null
@@ -1,14 +0,0 @@
-$NOLIST
-
-@CONST 1000
-
-@ADR #1000
-@VAR 1000
-
-@Sub1 9000
-@Sub2 9001
-
-
-@CALL M98P
-
-$LIST
diff --git a/client/testSource/lnk/Test.lnk b/client/testSource/lnk/Test.lnk
deleted file mode 100644
index 65c0d0c..0000000
--- a/client/testSource/lnk/Test.lnk
+++ /dev/null
@@ -1,5 +0,0 @@
-/* Macro Library file's
-CNC=..\lnk\xxx.MEX
-
-/* src file's
-FILE=Test
diff --git a/package-lock.json b/package-lock.json
index 32c7655..9f81108 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "macro-executor",
- "version": "0.1.3",
+ "version": "0.1.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 31bf321..45cbd5c 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "macro-executor",
"displayName": "Macro Executor Language",
"description": "Fanuc Macro-Executor Programming Language",
- "version": "0.1.4",
+ "version": "0.1.6",
"author": "iSorp",
"publisher": "iSorp",
"license": "MIT",
@@ -41,7 +41,8 @@
],
"extensions": [
".src",
- ".def"
+ ".def",
+ ".lnk"
],
"configuration": "./syntaxes/macro.configuration.json"
}
@@ -208,8 +209,8 @@
"@types/mocha": "^7.0.1",
"@types/node": "^12.11.7",
"@typescript-eslint/parser": "^2.3.0",
+ "eslint": "^6.4.0",
"mocha": "^7.0.1",
- "typescript": "^3.8.3",
- "eslint": "^6.4.0"
+ "typescript": "^3.8.3"
}
}
diff --git a/scripts/e2e.sh b/scripts/e2e.sh
index 860c62e..0bf18cb 100644
--- a/scripts/e2e.sh
+++ b/scripts/e2e.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
export CODE_TESTS_PATH="$(pwd)/client/out/test"
-export CODE_TESTS_WORKSPACE="$(pwd)/client/testFixture"
+export CODE_TESTS_WORKSPACE="$(pwd)/client/testSource"
node "$(pwd)/client/out/test/runTest"
\ No newline at end of file
diff --git a/server/package.json b/server/package.json
index 2ba589b..a57878d 100644
--- a/server/package.json
+++ b/server/package.json
@@ -1,7 +1,7 @@
{
"name": "lsp-macro-server",
"description": "Fanuc Macro-Executor Programming Language",
- "version": "0.0.1",
+ "version": "0.1.0",
"author": "iSorp",
"publisher": "iSorp",
"license": "MIT",
diff --git a/server/src/macroLanguageService/macroLanguageService.ts b/server/src/macroLanguageService/macroLanguageService.ts
index 51840bd..8223ee8 100644
--- a/server/src/macroLanguageService/macroLanguageService.ts
+++ b/server/src/macroLanguageService/macroLanguageService.ts
@@ -26,9 +26,9 @@ export interface LanguageService {
findDefinition(document: TextDocument, position: Position, macroFile: Macrofile): Location | null;
findReferences(document: TextDocument, position: Position, macroFile: Macrofile): Location[];
findImplementations(document: TextDocument, position: Position, macroFile: Macrofile): Location[];
- findDocumentLinks(document: TextDocument, stylesheet: Macrofile, documentContext: DocumentContext): DocumentLink[];
- findDocumentSymbols(document: TextDocument, stylesheet: Macrofile): SymbolInformation[];
- findCodeLenses(document: TextDocument, stylesheet: Macrofile): CodeLens[];
+ findDocumentLinks(document: TextDocument, macrofile: Macrofile, documentContext: DocumentContext): DocumentLink[];
+ findDocumentSymbols(document: TextDocument, macrofile: Macrofile): SymbolInformation[];
+ findCodeLenses(document: TextDocument, macrofile: Macrofile): CodeLens[];
}
function createFacade(parser: Parser, hover: MacroHover, navigation: MacroNavigation, validation: MacroValidation): LanguageService {
@@ -41,11 +41,11 @@ function createFacade(parser: Parser, hover: MacroHover, navigation: MacroNaviga
findImplementations: navigation.findImplementations.bind(navigation),
findDocumentLinks: navigation.findDocumentLinks.bind(navigation),
findDocumentSymbols: navigation.findDocumentSymbols.bind(navigation),
- findCodeLenses: navigation.findCodeLenses.bind(navigation),
+ findCodeLenses: navigation.findCodeLenses.bind(navigation)
};
}
-export function getMacroLanguageService(options?: LanguageServiceOptions): LanguageService {
+export function getMacroLanguageService(options: LanguageServiceOptions): LanguageService {
return createFacade(
new Parser(options && options.fileProvider),
diff --git a/server/src/macroLanguageService/macroLanguageTypes.ts b/server/src/macroLanguageService/macroLanguageTypes.ts
index f612f0b..edc8649 100644
--- a/server/src/macroLanguageService/macroLanguageTypes.ts
+++ b/server/src/macroLanguageService/macroLanguageTypes.ts
@@ -32,7 +32,7 @@ export interface FindDocumentLinks {
export interface LanguageServiceOptions {
- fileProvider?: MacroFileProvider;
+ fileProvider: MacroFileProvider;
}
export interface MacroFileType {
diff --git a/server/src/macroLanguageService/parser/macroErrors.ts b/server/src/macroLanguageService/parser/macroErrors.ts
index 92c876d..fb21822 100644
--- a/server/src/macroLanguageService/parser/macroErrors.ts
+++ b/server/src/macroLanguageService/parser/macroErrors.ts
@@ -34,6 +34,7 @@ export const ParseError = {
OperatorExpected: new MacroIssueType('macro-operatorexpected', localize('expected.operator', 'operator expected')),
IdentifierExpected: new MacroIssueType('macro-identifierexpected', localize('expected.ident', 'identifier expected')),
AddressExpected: new MacroIssueType('macro-addressexpected', localize('expected.address', 'address expected')),
+ ValueExpected: new MacroIssueType('macro-valueexpected', localize('expected.value', 'value expected')),
BodyExpected: new MacroIssueType('macro-bodyexpected', localize('expected.body', 'body expected')),
LabelExpected: new MacroIssueType('macro-labelexpected', localize('expected.label', 'label expected')),
TermExpected: new MacroIssueType('macro-termexpected', localize('expected.term', 'term expected')),
diff --git a/server/src/macroLanguageService/parser/macroNodes.ts b/server/src/macroLanguageService/parser/macroNodes.ts
index 9168247..10e734b 100644
--- a/server/src/macroLanguageService/parser/macroNodes.ts
+++ b/server/src/macroLanguageService/parser/macroNodes.ts
@@ -421,6 +421,8 @@ export enum NodeType {
Undefined,
MacroFile,
DefFile,
+ LnkFile,
+ LinkNode,
Include,
StringLiteral,
Declarations,
@@ -453,6 +455,38 @@ export enum NodeType {
SequenceNumber,
}
+export class LnkFile extends Node {
+
+ constructor(offset: number, length: number) {
+ super(offset, length);
+ }
+
+ public get type(): NodeType {
+ return NodeType.LnkFile;
+ }
+}
+
+export class LinkNode extends Node {
+
+ file?: Node;
+
+ constructor(offset: number, length: number) {
+ super(offset, length);
+ }
+
+ public get type(): NodeType {
+ return NodeType.LinkNode;
+ }
+
+ public setFile(node: Node | null): node is Node {
+ return this.setNode('file', node, 0);
+ }
+
+ public getFile(): Node | undefined {
+ return this.file;
+ }
+}
+
export class MacroFile extends Node {
constructor(offset: number, length: number) {
@@ -932,6 +966,7 @@ export class Assignment extends Node {
export enum ValueType {
String = 'string',
Numeric = 'numeric',
+ Constant = 'constant', // same as numeric in capital
MacroValue = 'value',
Address = 'address',
MFunc= 'm-function',
diff --git a/server/src/macroLanguageService/parser/macroParser.ts b/server/src/macroLanguageService/parser/macroParser.ts
index ecdc72b..e43b335 100644
--- a/server/src/macroLanguageService/parser/macroParser.ts
+++ b/server/src/macroLanguageService/parser/macroParser.ts
@@ -20,17 +20,15 @@ export interface IMark {
export class Parser {
-
private scanner: Scanner = new Scanner();
private textProvider?: nodes.ITextProvider;
private token: IToken;
private prevToken?: IToken;
private lastErrorToken?: IToken;
-
- private imports:string[] = [];
private declarations:Map = new Map()
+ private includes:string[] = []
- constructor(private fileProvider?: MacroFileProvider) {
+ constructor(private fileProvider: MacroFileProvider) {
this.token = { type: TokenType.EOF, offset: -1, len: 0, text: '' };
this.prevToken = undefined;
}
@@ -303,12 +301,13 @@ export class Parser {
//#region handle declaraions
private resolveIncludes(node:nodes.Include) {
let uri = node.getData('uri');
- if (!uri) {return;}
+ if (!uri) {
+ return;
+ }
- this.imports.push(uri);
let declaration = this.fileProvider?.get(uri);
-
if (declaration) {
+ this.includes.push(declaration.document.uri);
(declaration?.macrofile).accept(candidate => {
let found = false;
if (candidate.type === nodes.NodeType.VariableDef || candidate.type === nodes.NodeType.labelDef) {
@@ -350,6 +349,7 @@ export class Parser {
const symbol = this.create(nodes.Symbol);
symbol.referenceTypes = [nodes.ReferenceType.Variable];
+ const isUpperCase = this.token.text == this.token.text.toUpperCase();
if (!this.accept(TokenType.Symbol)){
return this.finish(node, ParseError.IdentifierExpected);
@@ -358,9 +358,27 @@ export class Parser {
const value = this.createNode(nodes.NodeType.DeclarationValue);
-
- if (node.setValue(this.parseNumeric())){
- node.valueType = nodes.ValueType.Numeric;
+ if (this.peekDelim('+') || this.peekDelim('-')) {
+ this.consumeToken();
+ if (node.setValue(this.parseNumeric())){
+ if (isUpperCase){
+ node.valueType = nodes.ValueType.Constant;
+ }
+ else {
+ node.valueType = nodes.ValueType.Numeric;
+ }
+ }
+ else{
+ return this.finish(node, ParseError.ValueExpected);
+ }
+ }
+ else if (node.setValue(this.parseNumeric())) {
+ if (isUpperCase){
+ node.valueType = nodes.ValueType.Constant;
+ }
+ else {
+ node.valueType = nodes.ValueType.Numeric;
+ }
}
else if (this.accept(TokenType.Hash)){
if (node.setValue(this.parseNumeric())){
@@ -437,22 +455,28 @@ export class Parser {
// #region Global scope
public parseMacroFile(textDocument: TextDocument): nodes.MacroFile {
+ this.declarations.clear();
+ this.includes = [];
const versionId = textDocument.version;
const text = textDocument.getText();
this.textProvider = (offset: number, length: number) => {
- // TODO no idea why the versions are not equivalent
- /*if (textDocument.version !== versionId) {
+ if (textDocument.version !== versionId) {
throw new Error('Underlying model has changed, AST is no longer valid');
- }*/
+ }
return text.substr(offset, length);
};
- let type = textDocument.uri.split('.').pop();
- if (type?.toLocaleLowerCase() === 'def'){
+ let type = textDocument.uri.split('.').pop()?.toLocaleLowerCase() ;
+ if (type === 'def'){
return this.internalParse(text, this._parseDefFile, this.textProvider);
}
- else{
+ else if (type === 'src'){
return this.internalParse(text, this._parseMacroFile, this.textProvider);
+ }
+ else if (type === 'lnk'){
+ return this.internalParse(text, this._parseLnkFile, this.textProvider);
}
+
+ return this.createNode(nodes.NodeType.Undefined);
}
public internalParse(input: string, parseFunc: () => U, textProvider?: nodes.ITextProvider): U {
@@ -469,6 +493,59 @@ export class Parser {
return node;
}
+ public _parseLnkFile(): nodes.MacroFile {
+ const node = this.createNode(nodes.NodeType.DefFile);
+ let hasMatch = false;
+ do {
+ do {
+ let child = null;
+ hasMatch = false;
+ child = this._parseLinkNode();
+ if (child){
+ node.addChild(child);
+ hasMatch = true;
+ }
+ } while (hasMatch);
+
+ if (this.peek(TokenType.EOF)) {
+ break;
+ }
+
+ /*let child = this.parseUnexpected();
+ if (child){
+ node.addChild(child);
+ hasMatch = true;
+ }*/
+ this.consumeToken();
+
+ } while (!this.peek(TokenType.EOF));
+ return this.finish(node);
+ }
+
+ public _parseLinkNode() : nodes.LinkNode | null {
+ let node = this.create(nodes.LinkNode);
+
+ if (!this.peekKeyword('file')){
+ return null;
+ }
+
+ this.consumeToken();
+
+ if (!this.acceptDelim('=')) {
+ this.finish(node, ParseError.EqualExpected, [TokenType.NewLine]);
+ }
+
+ if (this.peek(TokenType.Symbol)){
+ let file = this.create(nodes.Node);
+ this.acceptUnquotedString();
+ node.setFile(this.finish(file));
+ }else{
+ this.finish(node, ParseError.EqualExpected, [TokenType.NewLine]);
+ }
+
+ return this.finish(node);
+ }
+
public _parseDefFile(): nodes.MacroFile {
const node = this.createNode(nodes.NodeType.DefFile);
let hasMatch = false;
@@ -544,6 +621,8 @@ export class Parser {
this.consumeToken();
} while (!this.peek(TokenType.EOF));
+
+ node.setData('includes', this.includes);
return this.finish(node);
}
diff --git a/server/src/macroLanguageService/services/lint.ts b/server/src/macroLanguageService/services/lint.ts
index 214158f..32d4a26 100644
--- a/server/src/macroLanguageService/services/lint.ts
+++ b/server/src/macroLanguageService/services/lint.ts
@@ -15,9 +15,8 @@ const _dot = '.'.charCodeAt(0);
export class LintVisitor implements nodes.IVisitor {
- static entries(macrofile: nodes.Node, document: TextDocument, fileProvider?: MacroFileProvider): nodes.IMarker[] {
+ static entries(macrofile: nodes.Node, document: TextDocument, fileProvider: MacroFileProvider): nodes.IMarker[] {
const visitor = new LintVisitor(macrofile, fileProvider);
- visitor.LoadIncludes(macrofile);
macrofile.acceptVisitor(visitor);
return visitor.getEntries();
}
@@ -25,11 +24,12 @@ export class LintVisitor implements nodes.IVisitor {
private declarations:Map = new Map()
private sequenceNumbers:FunctionMap = new FunctionMap();
private labelList:FunctionMap = new FunctionMap();
+ private imports: string[] = [];
private rules: nodes.IMarker[] = [];
- private imports:string[] = [];
+ private functionList = new Array();
- private constructor(private macrofile: nodes.Node, private fileProvider?: MacroFileProvider) { }
+ private constructor(private macrofile: nodes.Node, private fileProvider: MacroFileProvider) { }
public getEntries(filter: number = (nodes.Level.Warning | nodes.Level.Error)): nodes.IMarker[] {
return this.rules.filter(entry => {
@@ -46,6 +46,8 @@ export class LintVisitor implements nodes.IVisitor {
switch (node.type) {
case nodes.NodeType.MacroFile:
return this.visitGlobalScope(node);
+ case nodes.NodeType.Include:
+ return this.visitIncludes(node);
case nodes.NodeType.Symbol:
return this.visitSymbols(node);
case nodes.NodeType.Variable:
@@ -66,28 +68,11 @@ export class LintVisitor implements nodes.IVisitor {
return true;
}
- private LoadIncludes(node:nodes.MacroFile) {
- node.accept(candidate => {
- if (candidate.type === nodes.NodeType.Include) {
- this.visitInclude(candidate);
- return false;
- }
- return true;
- });
- }
-
- private visitGlobalScope(node: nodes.Node) : boolean {
- for (const element of node.getChildren()) {
- if (element.type === nodes.NodeType.Symbol){
- this.addEntry(element, Rules.IllegalStatement);
- }
- }
- return true;
- }
-
- private visitInclude(node: nodes.Include) {
+ private visitIncludes(node: nodes.Include) : boolean {
let uri = node.getChild(0);
- if (!uri) {return;}
+ if (!uri) {
+ return false;
+ }
if (this.imports.indexOf(uri?.getText()) > -1){
this.addEntry(node, Rules.DuplicateDeclarations);
@@ -108,12 +93,20 @@ export class LintVisitor implements nodes.IVisitor {
}
else {
this.addEntry(node, Rules.IncludeNotFound);
- return;
}
}
+ return false;
+ }
+
+ private visitGlobalScope(node: nodes.Node) : boolean {
+ for (const element of node.getChildren()) {
+ if (element.type === nodes.NodeType.Symbol){
+ this.addEntry(element, Rules.IllegalStatement);
+ }
+ }
+ return true;
}
- private functionList = new Array();
private visitFunction(node: nodes.Function): boolean {
let ident = node.getIdentifier();
@@ -184,11 +177,11 @@ export class LintVisitor implements nodes.IVisitor {
this.addEntry(def, Rules.DuplicateDeclarations);
}
- for (const element of this.declarations.values()){
+ /*for (const element of this.declarations.values()){
if (element.getValue()?.getText() === def.getValue()?.getText()){
- //this.addEntry(def, Rules.DuplicateAddress);
+ this.addEntry(def, Rules.DuplicateAddress);
}
- }
+ }*/
if (node.type === nodes.NodeType.VariableDef) {
this.declarations.set(ident, node);
diff --git a/server/src/macroLanguageService/services/macroHover.ts b/server/src/macroLanguageService/services/macroHover.ts
index 9b865f1..1fdb3c4 100644
--- a/server/src/macroLanguageService/services/macroHover.ts
+++ b/server/src/macroLanguageService/services/macroHover.ts
@@ -12,7 +12,7 @@ import { TextDocument, Range, Position, Location, Hover, MarkedString, MarkupCon
export class MacroHover {
- constructor(private fileProvider?: MacroFileProvider | undefined) { }
+ constructor(private fileProvider: MacroFileProvider) { }
public doHover(document: TextDocument, position: Position, macroFile: nodes.MacroFile): Hover | null {
function getRange(node: nodes.Node) {
diff --git a/server/src/macroLanguageService/services/macroNavigation.ts b/server/src/macroLanguageService/services/macroNavigation.ts
index ba2117d..32f8c69 100644
--- a/server/src/macroLanguageService/services/macroNavigation.ts
+++ b/server/src/macroLanguageService/services/macroNavigation.ts
@@ -14,16 +14,26 @@ import { Symbols, Symbol } from '../parser/macroSymbolScope';
import { CodeLens } from 'vscode-languageserver';
+class FunctionMap {
+ private elements:Map = new Map();
+ public add(key:string, value:string){
+ if (!this.elements.has(key)){
+ this.elements.set(key, new Array());
+ }
+ this.elements.get(key)?.push(value);
+ }
+
+ public get(key:string) : string[] | undefined {
+ return this.elements.get(key);
+ }
+}
+
export class MacroNavigation {
- constructor(private fileProvider?: MacroFileProvider){}
+ constructor(private fileProvider: MacroFileProvider){}
public findDefinition(document: TextDocument, position: Position, macroFile: nodes.Node): Location | null {
- let includes:string[] = [];
- if (this.fileProvider){
- includes = this.getIncludeUris(macroFile, this.fileProvider);
- }
-
+ const includes = this.getIncludeUris(macroFile, this.fileProvider);
includes.push(document.uri);
const offset = document.offsetAt(position);
const node = nodes.getNodeAtOffset(macroFile, offset);
@@ -168,24 +178,24 @@ export class MacroNavigation {
return Range.create(document.positionAt(node.offset), document.positionAt(node.end));
}
- let codeLenses: CodeLens[] = [];
- let declarations:nodes.Node[] = [];
-
- if (!this.fileProvider){
- return codeLenses;
- }
+ const codeLenses: CodeLens[] = [];
+ const declarations:FunctionMap = new FunctionMap();
// local search
if (macroFile.type === nodes.NodeType.MacroFile) {
macroFile.accept(candidate => {
if (candidate.type === nodes.NodeType.Variable || candidate.type === nodes.NodeType.label) {
- declarations.push(candidate);
- }
+ const node = (candidate).getSymbol();
+ if (node) {
+ declarations.add(node.getText(), node.getText());
+ }
+ return false;
+ }
return true;
});
}
// global search
- {
+ else {
const types = this.fileProvider?.getAll();
for (const type of types) {
@@ -194,16 +204,19 @@ export class MacroNavigation {
}
if ((type.macrofile).type === nodes.NodeType.MacroFile) {
- let includes = this.getIncludeUris(type.macrofile, this.fileProvider);
+ const includes = this.getIncludeUris(type.macrofile, this.fileProvider);
if (includes.filter(uri => uri === document.uri).length <= 0) {
continue;
}
}
-
(type.macrofile).accept(candidate => {
if (candidate.type === nodes.NodeType.Variable || candidate.type === nodes.NodeType.label) {
- declarations.push(candidate);
+ const node = (candidate).getSymbol();
+ if (node) {
+ declarations.add(node.getText(), node.getText());
+ }
+ return false;
}
return true;
});
@@ -211,39 +224,34 @@ export class MacroNavigation {
}
(macroFile).accept(candidate => {
- if (candidate.type === nodes.NodeType.VariableDef || candidate.type === nodes.NodeType.labelDef /*|| candidate.type === nodes.NodeType.Function*/) {
-
- let node:nodes.Node | undefined;
- /*if (candidate.type === nodes.NodeType.Function){
- node = (candidate).getIdentifier();
- }*/
- {
- node = (candidate).getSymbol();
- }
- let count = declarations.filter(a => (>a).getSymbol()?.getText() === node?.getText());
+ if (candidate.type === nodes.NodeType.VariableDef || candidate.type === nodes.NodeType.labelDef) {
+ const node = (candidate).getSymbol();
if (node) {
- let pos = document.positionAt(node.offset);
+ const value = declarations.get(node.getText()); //declarations.filter(a => a === node?.getText());
+ const count = value?.length;
+ const pos = document.positionAt(node.offset);
+ const c = count === undefined ? 0 : count;
codeLenses.push(
{
range: getRange(node),
data: {
- title: count.length + (count.length > 1 ? ' references found' : ' reference found'),
+ title: c + (c !== 1 ? ' references' : ' reference'),
uri:document.uri,
line:pos.line,
character:pos.character,
type: MacroCodeLensCommand.References
}
});
- return false;
}
+ return false;
}
return true;
});
return codeLenses;
}
-
+
private uriLiteralNodeToDocumentLink(document: TextDocument, uriLiteralNode: nodes.Node, documentContext: DocumentContext): DocumentLink | null {
if (uriLiteralNode.getChildren().length === 0) {
return null;
@@ -313,22 +321,18 @@ export class MacroNavigation {
private findGlobalReferences(includeUri:string, position:Position, node:nodes.Node, implType:nodes.ReferenceType | undefined = undefined):Location[] {
let locations:Location[] = [];
- let declarations:MacroFileType[] = [];
-
- if (this.fileProvider){
- declarations = this.fileProvider?.getAll();
- }
+ let declarations = this.fileProvider?.getAll();
for (const type of declarations) {
// only accept the origin def file
- if (((type.macrofile).type === nodes.NodeType.DefFile && includeUri !== type.document.uri.toLocaleLowerCase())){
+ if (((type.macrofile).type === nodes.NodeType.DefFile && includeUri !== type.document.uri)){
continue;
}
// only accept a src file which includes the origin def file
- if (this.fileProvider && (type.macrofile).type === nodes.NodeType.MacroFile) {
- let includes = this.getIncludeUris(type.macrofile, this.fileProvider);
+ if ((type.macrofile).type === nodes.NodeType.MacroFile) {
+ const includes = this.getIncludeUris(type.macrofile, this.fileProvider);
if (includes.filter(uri => uri === includeUri).length <= 0){
continue;
}
@@ -342,7 +346,7 @@ export class MacroNavigation {
const name = node.getText();
// only accept the symbol of the origin symbol source file
- if (symbol && includeUri !== type.document.uri.toLocaleLowerCase()) {
+ if (symbol && includeUri !== type.document.uri) {
continue; // local declaration found
}
@@ -379,13 +383,19 @@ export class MacroNavigation {
return locations;
}
+ /**
+ * Finds the uri of the declaration origin of a certain node
+ * @param document
+ * @param node
+ * @param macroFile
+ */
private findIncludeUri(document: TextDocument, node: nodes.Node, macroFile: nodes.Node): string | null {
- let includes:string[] = [];
-
- if (this.fileProvider){
- includes = this.getIncludeUris(macroFile, this.fileProvider);
+ let includes:string[] = []
+ const uris = this.getIncludeUris(macroFile, this.fileProvider);
+ if (uris) {
+ includes = uris;
}
-
+
includes.push(document.uri);
if (!node) {
return null;
@@ -408,22 +418,13 @@ export class MacroNavigation {
return null;
}
+ /**
+ * Gets the include uris of a macrofile
+ * @param macroFile
+ * @param fileProvider
+ */
private getIncludeUris(macroFile: nodes.MacroFile, fileProvider:MacroFileProvider) : string[] {
- let includeUris:string[] = [];
- macroFile.accept(candidate => {
- if (candidate.type === nodes.NodeType.Include) {
- const uriStringNode = candidate.getChild(0);
- if (uriStringNode) {
- const f = fileProvider.getLink(uriStringNode?.getText());
- if (f){
- includeUris.push(f);
- }
- }
- return false;
- }
- return true;
- });
- return includeUris;
+ return macroFile.getData('includes');
}
private getHighlightKind(node: nodes.Node): DocumentHighlightKind {
diff --git a/server/src/macroLanguageService/services/macroValidation.ts b/server/src/macroLanguageService/services/macroValidation.ts
index efbcda0..4fedb74 100644
--- a/server/src/macroLanguageService/services/macroValidation.ts
+++ b/server/src/macroLanguageService/services/macroValidation.ts
@@ -12,7 +12,7 @@ export class MacroValidation {
private settings?: LanguageSettings;
- constructor( private fileProvider?: MacroFileProvider) {}
+ constructor( private fileProvider: MacroFileProvider) {}
public configure(settings?: LanguageSettings) {
this.settings = settings;
diff --git a/server/src/server.ts b/server/src/server.ts
index 117e79a..44f4c37 100644
--- a/server/src/server.ts
+++ b/server/src/server.ts
@@ -5,7 +5,7 @@
'use strict';
import * as path from 'path';
-import { readFileSync} from 'fs';
+import { readFileSync, existsSync } from 'fs';
import { LanguageSettings, MacroFileProvider, FindDocumentLinks, Range } from './macroLanguageService/macroLanguageTypes';
import { Parser } from './macroLanguageService/parser/macroParser';
@@ -31,6 +31,8 @@ import {
FileChangeType,
DidChangeWatchedFilesParams,
CodeLensParams,
+ CodeLens,
+ TextDocumentChangeEvent,
} from 'vscode-languageserver';
import {
@@ -68,7 +70,7 @@ let globalSettings: LanguageSettings = defaultSettings;
class FileProvider implements MacroFileProvider {
- private links = new Links();
+ private resolver = new Links();
get(file: string): MacroFileType | undefined {
@@ -76,16 +78,20 @@ class FileProvider implements MacroFileProvider {
return undefined;
}
- let uri = this.links.resolveReference(file);
+ let uri = this.resolver.resolveReference(file);
if (uri) {
- let doc = getParsedDocument(uri, new Parser(this).parseMacroFile);
+
+ let doc = getParsedDocument(uri, (document => {
+ let parser = new Parser(this);
+ return parser.parseMacroFile(document);
+ }));
if (!doc) {
try {
const file = readFileSync(Files.uriToFilePath(uri)!, 'utf-8');
let document = TextDocument.create(uri!, 'macro', 1, file.toString());
try {
+
let macrofile = new Parser(this).parseMacroFile(document);
-
doc = {
macrofile: macrofile,
document: document,
@@ -110,12 +116,11 @@ class FileProvider implements MacroFileProvider {
}
getAll(): MacroFileType[] {
-
let types:MacroFileType[] = [];
try {
- if (workspaceFolder){
+ if (workspaceFolder) {
let dir = Files.uriToFilePath(workspaceFolder);
- let files = glob.sync(dir+'/**/*.{[sS][rR][cC],[dD][eE][fF]}');
+ let files = glob.sync(dir+'/**/*.{[sS][rR][cC],[dD][eE][fF],[lL][nN][kK]}');
for (const file of files) {
let type = this.get(file);
if (type){
@@ -126,16 +131,21 @@ class FileProvider implements MacroFileProvider {
}catch (err){
connection.console.log(err);
}
-
return types;
}
getLink(ref:string) : string |undefined {
- return this.links.resolveReference(ref);
+ return this.resolver.resolveReference(ref);
}
}
class Links implements FindDocumentLinks {
+
+ /**
+ * Returns a given path as uri
+ * @param ref
+ * @param base
+ */
public resolveReference(ref: string, base?: string): string | undefined {
if (!workspaceFolder){
return '';
@@ -159,26 +169,20 @@ class Links implements FindDocumentLinks {
return undefined;
}
- file = path.normalize(file.toLocaleLowerCase());
-
- // Workaround to get the case-sensitive path of a non case-sensitive path
- // TODO find correct solution
- let files = glob.sync(Files.uriToFilePath(workspaceFolder)+'/**/*.{[sS][rR][cC],[dD][eE][fF]}');
- let filter = files.filter(a => {
- let b = path.normalize(a.toLocaleLowerCase());
- if (b === file){
- return true;
- }
- else {
- return false;
- }
- });
+ file = this.resolvePathCaseSensitive(file);
- if (filter && filter.length > 0) {
- return URI.file(filter[0]).toString();
+ if (file) {
+ return URI.file(file).toString();
}
else {return '';}
}
+
+ private resolvePathCaseSensitive(file:string) {
+ let norm = path.normalize(file)
+ let root = path.parse(norm).root
+ let p = norm.slice(Math.max(root.length - 1, 0))
+ return glob.sync(p, { nocase: true, cwd: root })[0]
+ }
}
const macroLanguageService = getMacroLanguageService({
@@ -232,27 +236,11 @@ connection.onInitialized(async () => {
connection.client.register(DidChangeConfigurationNotification.type, undefined);
}
settings = await getSettings();
- validateWorkspace();
+ Promise.resolve(revalidate(true));
});
connection.onCodeLens(codelens);
-connection.onCodeLensResolve(handler => {
- let data:MacroCodeLensType = handler.data;
- let command:string = '';
- if (data.type === MacroCodeLensCommand.References){
- command = 'macro.codelens.references';
- }
-
- return {
- range: handler.range,
- command: {
- command: command,
- title:data.title,
- arguments: [data.line, data.character]
- }
- };
-});
-
+connection.onCodeLensResolve(codeLensResolve);
connection.onDidChangeWatchedFiles(watchedFiles);
connection.onDidChangeConfiguration(configuration);
connection.onDefinition(definition);
@@ -262,14 +250,7 @@ connection.onDocumentSymbol(documentSymbol);
connection.onDocumentLinks(documentLinks);
connection.onHover(hower);
-documents.onDidChangeContent(change => {
- validateTextDocument(getParsedDocument(change.document.uri, macroLanguageService.parseMacroFile));
-
- if (change.document.uri.split('.').pop()?.toLocaleLowerCase() === 'def') {
- Promise.resolve(validateOpenDocuments());
- }
-});
-
+documents.onDidChangeContent(content);
documents.listen(connection);
connection.listen();
@@ -296,11 +277,19 @@ function getSettings(): Thenable {
return result;
}
+function content(change:TextDocumentChangeEvent) {
+ validateTextDocument(getParsedDocument(change.document.uri, macroLanguageService.parseMacroFile));
+
+ // TODO validate files only which include this def file
+ if (change.document.uri.split('.').pop()?.toLocaleLowerCase() === 'def') {
+ Promise.resolve(revalidate(false));
+ }
+}
+
function hower(params: TextDocumentPositionParams) {
let repo = getParsedDocument(params.textDocument.uri, macroLanguageService.parseMacroFile);
if (!repo) {return null;}
return macroLanguageService.doHover(repo.document, params.position, repo.macrofile);
-
}
function codelens(params: CodeLensParams) {
@@ -313,6 +302,24 @@ function codelens(params: CodeLensParams) {
}
}
+function codeLensResolve(handler:CodeLens) {
+
+ let data:MacroCodeLensType = handler.data;
+ let command:string = '';
+ if (data.type === MacroCodeLensCommand.References){
+ command = 'macro.codelens.references';
+ }
+
+ return {
+ range: handler.range,
+ command: {
+ command: command,
+ title:data.title,
+ arguments: [data.line, data.character]
+ }
+ };
+}
+
function definition(params: DefinitionParams) {
let repo = getParsedDocument(params.textDocument.uri, macroLanguageService.parseMacroFile);
if (!repo) {return null;}
@@ -329,14 +336,12 @@ function implementations(params: ImplementationParams) {
let repo = getParsedDocument(params.textDocument.uri, macroLanguageService.parseMacroFile);
if (!repo) {return null;}
return macroLanguageService.findImplementations(repo.document, params.position, repo.macrofile);
- return null;
}
function documentSymbol(params: DocumentSymbolParams) {
let repo = getParsedDocument(params.textDocument.uri, macroLanguageService.parseMacroFile);
if (!repo) {return null;}
return macroLanguageService.findDocumentSymbols(repo.document, repo.macrofile);
- return null;
}
function documentLinks(params: DocumentLinkParams) {
@@ -345,29 +350,17 @@ function documentLinks(params: DocumentLinkParams) {
return macroLanguageService.findDocumentLinks(repo.document, repo.macrofile, new Links());
}
-async function validateTextDocument(doc: MacroFileType | undefined) {
+function validateTextDocument(doc: MacroFileType | undefined) {
if (!doc) {return;}
try {
- let settings = await getSettings();
- const diagnostics: Diagnostic[] = [];
- if (doc.document.languageId === 'macro') {
-
- if (doc.document) {
- if (macroLanguageService.doValidation && doc.macrofile) {
- let entries = macroLanguageService.doValidation(doc.document, doc.macrofile, settings);
- let index = 0;
- for (const entry of entries) {
- if (maxNumberOfProblems <= index){
- break;
- }
- diagnostics.push(entry);
- ++index;
- }
- connection.sendDiagnostics({ uri: doc.document.uri, diagnostics });
- }
+ if (doc.document) {
+ if (macroLanguageService.doValidation && doc.macrofile) {
+ const entries = macroLanguageService.doValidation(doc.document, doc.macrofile, settings);
+ const diagnostics: Diagnostic[] = entries;
+ connection.sendDiagnostics({ uri: doc.document.uri, diagnostics });
}
}
} catch (e) {
@@ -376,47 +369,53 @@ async function validateTextDocument(doc: MacroFileType | undefined) {
}
}
-async function validateWorkspace() {
- if (settings && settings?.validate?.workspace){
- let fp = new FileProvider();
- let types = fp.getAll();
- for (const element of types){
- validateTextDocument(element);
- }
- }
-}
-
-async function validateOpenDocuments() {
- for (const document of documents.all()){
- validateTextDocument(getParsedDocument(document.uri, macroLanguageService.parseMacroFile));
- }
-}
-
function watchedFiles(handler:DidChangeWatchedFilesParams){
for (const file of handler.changes) {
if (file.type === FileChangeType.Deleted){
- parsedDocuments.delete(file.uri);
- validateWorkspace();
+ parsedDocuments.clear();
+ revalidate(true);
}
else if (file.type === FileChangeType.Changed) {
- // if the file is not opened it was changed extern.
- // n the parsedDocuments repo it is not up-to-date anymore.
+ // if the file is not opened it was changed external
+ // so the parsedDocuments repo is not up-to-date anymore.
if (!documents.get(file.uri)){
parsedDocuments.delete(file.uri);
}
}
else if (file.type === FileChangeType.Created) {
- validateWorkspace();
+ parsedDocuments.clear();
+ revalidate(true);
}
}
}
-function getParsedDocument(uri: string, parser:((document:TextDocument) => Macrofile)) : MacroFileType | undefined {
+function revalidate(workspace:boolean) {
+ if (workspace && settings && settings?.validate?.workspace){
+ let fp = new FileProvider();
+ let types = fp.getAll();
+ for (const element of types){
+ validateTextDocument(element);
+ }
+ } else{
+ for (const document of documents.all()){
+ validateTextDocument(getParsedDocument(document.uri, macroLanguageService.parseMacroFile,true));
+ }
+ }
+}
+
+/**
+ * Returns the parsed version of a TextDocument.
+ * An open TextDocument will be reparsd if the Version is newer than the parsed version.
+ * @param uri
+ * @param parser
+ * @param parse
+ */
+function getParsedDocument(uri: string, parser:((document:TextDocument) => Macrofile), parse:boolean=false) : MacroFileType | undefined {
let document = documents.get(uri);
if (document) {
let parsed = parsedDocuments.get(uri);
if (parsed) {
- if (document.version !== parsed.version){
+ if (document.version !== parsed.version || parse){
parsedDocuments.set(uri , {
macrofile: parser(document),
document: document,