diff --git a/package-lock.json b/package-lock.json index 07d5414f..373bad56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "antlr4ts": "^0.5.0-alpha.4", "axios": "^1.4.0", "azure-arm-resource": "^7.4.0", + "chevrotain": "10.5.0", "csv-parser": "^3.0.0", "esbuild-plugin-copy": "^2.1.1", "extract-zip": "^2.0.1", @@ -397,6 +398,35 @@ "node": ">=6.9.0" } }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==" + }, + "node_modules/@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1712,6 +1742,19 @@ "node": ">=0.4.0" } }, + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4642,6 +4685,11 @@ "node": ">=8.10.0" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -6026,6 +6074,35 @@ "to-fast-properties": "^2.0.0" } }, + "@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "requires": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "requires": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==" + }, + "@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==" + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -7069,6 +7146,19 @@ "integrity": "sha512-4uF+X0GMPac82R3P1bZJCxDbEaJZvveosJELubRPC7mW8uku/PJLEl+Gv1TIR2YKOyDaoEmAcfw5/s2g8cJbhQ==", "dev": true }, + "chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "requires": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -9261,6 +9351,11 @@ "picomatch": "^2.2.1" } }, + "regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", diff --git a/package.json b/package.json index ca8e2411..fa20d060 100644 --- a/package.json +++ b/package.json @@ -659,6 +659,7 @@ "antlr4ts": "^0.5.0-alpha.4", "axios": "^1.4.0", "azure-arm-resource": "^7.4.0", + "chevrotain": "^10.5.0", "csv-parser": "^3.0.0", "esbuild-plugin-copy": "^2.1.1", "extract-zip": "^2.0.1", diff --git a/server/src/parser/lexer.ts b/server/src/parser/lexer.ts new file mode 100644 index 00000000..039bb2ff --- /dev/null +++ b/server/src/parser/lexer.ts @@ -0,0 +1,87 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { Lexer } from "chevrotain"; +import { + BinaryLiteral, + ByteLiteral, + CharLiteral, + DateLiteral, + DateTimeLiteral, + FloatLiteral, + IntegerLiteral, + MiliTimeLiteral, + MinuteLiteral, + MonthLiteral, + NanoTimeLiteral, + SecondLiteral, + TimeStampLiteral, +} from "./literals"; +import { + BinaryColon, + BlockComment, + Colon, + Dot, + EndOfLine, + Identifier, + Infix, + Keyword, + LBracket, + LCurly, + LParen, + LineComment, + Quote, + RBracket, + RCurly, + RParen, + SemiColon, + Underscore, + WhiteSpace, +} from "./tokens"; + +export const QTokens = [ + BlockComment, + LineComment, + CharLiteral, + WhiteSpace, + EndOfLine, + Quote, + SemiColon, + LParen, + RParen, + LBracket, + RBracket, + LCurly, + RCurly, + Dot, + Underscore, + TimeStampLiteral, + DateTimeLiteral, + MiliTimeLiteral, + NanoTimeLiteral, + DateLiteral, + MonthLiteral, + SecondLiteral, + MinuteLiteral, + FloatLiteral, + BinaryLiteral, + ByteLiteral, + IntegerLiteral, + Infix, + BinaryColon, + Colon, + Keyword, + Identifier, +]; + +export const QLexer = new Lexer(QTokens); diff --git a/server/src/parser/literals.ts b/server/src/parser/literals.ts new file mode 100644 index 00000000..280dbc7c --- /dev/null +++ b/server/src/parser/literals.ts @@ -0,0 +1,81 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { createToken } from "chevrotain"; + +export const CharLiteral = createToken({ + name: "CharLiteral", + pattern: /"(?:\\.|(?:(?!\r?\n\S)[^"]))*"/, +}); + +export const FloatLiteral = createToken({ + name: "FloatLiteral", + pattern: /-?(?:\d+\.\d+|\.\d+|\d+\.)(?:e[+-]?\d?\d)?e?/, +}); + +export const IntegerLiteral = createToken({ + name: "IntegerLiteral", + pattern: /-?\d+[jhi]?/, +}); + +export const BinaryLiteral = createToken({ + name: "BinaryLiteral", + pattern: /[01]+b/, +}); + +export const ByteLiteral = createToken({ + name: "ByteLiteral", + pattern: /0x(?:[0-9a-fA-F]{2})+/, +}); + +export const DateLiteral = createToken({ + name: "DateLiteral", + pattern: /\d{4}\.\d{2}\.\d{2}/, +}); + +export const MiliTimeLiteral = createToken({ + name: "MiliTimeLiteral", + pattern: /\d{2}:\d{2}:\d{2}\.\d{3}/, +}); + +export const NanoTimeLiteral = createToken({ + name: "NanoTimeLiteral", + pattern: /(?:0D)?\d{2}:\d{2}:\d{2}\.\d{9}/, +}); + +export const DateTimeLiteral = createToken({ + name: "DateTimeLiteral", + pattern: /\d{4}\.\d{2}\.\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}/, +}); + +export const TimeStampLiteral = createToken({ + name: "TimeStampLiteral", + pattern: /\d{4}\.\d{2}\.\d{2}D\d{2}:\d{2}:\d{2}\.\d{9}/, +}); + +export const MonthLiteral = createToken({ + name: "MonthLiteral", + pattern: /\d{4}\.\d{2}m/, + longer_alt: DateLiteral, +}); + +export const SecondLiteral = createToken({ + name: "SecondLiteral", + pattern: /\d{2}:\d{2}:\d{2}/, +}); + +export const MinuteLiteral = createToken({ + name: "MinuteLiteral", + pattern: /\d{2}:\d{2}/, + longer_alt: SecondLiteral, +}); diff --git a/server/src/parser/parser.ts b/server/src/parser/parser.ts new file mode 100644 index 00000000..f2526705 --- /dev/null +++ b/server/src/parser/parser.ts @@ -0,0 +1,160 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { CstParser } from "chevrotain"; +import { QLexer, QTokens } from "./lexer"; +import { + BinaryLiteral, + ByteLiteral, + CharLiteral, + DateLiteral, + DateTimeLiteral, + FloatLiteral, + IntegerLiteral, + MiliTimeLiteral, + MinuteLiteral, + MonthLiteral, + NanoTimeLiteral, + SecondLiteral, + TimeStampLiteral, +} from "./literals"; +import { + BinaryColon, + Colon, + Dot, + EndOfLine, + Identifier, + Infix, + Keyword, + LBracket, + LCurly, + LParen, + RBracket, + RCurly, + RParen, + SemiColon, + Underscore, +} from "./tokens"; + +class Parser extends CstParser { + constructor() { + super(QTokens, { recoveryEnabled: true }); + this.performSelfAnalysis(); + } + + public script = this.RULE("script", () => { + this.MANY(() => this.SUBRULE(this.statement)); + }); + + private statement = this.RULE("statement", () => { + this.MANY(() => this.SUBRULE(this.expression)); + this.SUBRULE(this.terminate); + }); + + private terminate = this.RULE("terminate", () => { + this.OR([ + { ALT: () => this.CONSUME(EndOfLine) }, + { ALT: () => this.CONSUME(SemiColon) }, + ]); + }); + + private expression = this.RULE("expression", () => { + this.OR([ + { ALT: () => this.SUBRULE(this.literal) }, + { ALT: () => this.SUBRULE(this.keyword) }, + { ALT: () => this.SUBRULE(this.list) }, + { ALT: () => this.SUBRULE(this.lambda) }, + { ALT: () => this.SUBRULE(this.bracket) }, + { ALT: () => this.SUBRULE(this.assignment) }, + { ALT: () => this.SUBRULE(this.infix) }, + { ALT: () => this.SUBRULE(this.identifier) }, + ]); + }); + + private list = this.RULE("list", () => { + this.CONSUME(LParen); + this.MANY_SEP({ + SEP: SemiColon, + DEF: () => this.MANY(() => this.SUBRULE(this.expression)), + }); + this.CONSUME(RParen); + }); + + private lambda = this.RULE("lambda", () => { + this.CONSUME(LCurly); + this.OPTION(() => this.SUBRULE(this.bracket)); + this.MANY_SEP({ + SEP: SemiColon, + DEF: () => this.MANY(() => this.SUBRULE(this.expression)), + }); + this.CONSUME(RCurly); + }); + + private bracket = this.RULE("bracket", () => { + this.CONSUME(LBracket); + this.MANY_SEP({ + SEP: SemiColon, + DEF: () => this.MANY(() => this.SUBRULE(this.expression)), + }); + this.CONSUME(RBracket); + }); + + private assignment = this.RULE("assignment", () => { + this.OPTION(() => this.SUBRULE(this.infix)); + this.CONSUME(Colon); + this.SUBRULE(this.expression); + }); + + private infix = this.RULE("infix", () => { + this.CONSUME(Infix); + }); + + private literal = this.RULE("literal", () => { + this.OR([ + { ALT: () => this.CONSUME(CharLiteral) }, + { ALT: () => this.CONSUME(TimeStampLiteral) }, + { ALT: () => this.CONSUME(DateTimeLiteral) }, + { ALT: () => this.CONSUME(MiliTimeLiteral) }, + { ALT: () => this.CONSUME(NanoTimeLiteral) }, + { ALT: () => this.CONSUME(DateLiteral) }, + { ALT: () => this.CONSUME(MonthLiteral) }, + { ALT: () => this.CONSUME(SecondLiteral) }, + { ALT: () => this.CONSUME(MinuteLiteral) }, + { ALT: () => this.CONSUME(FloatLiteral) }, + { ALT: () => this.CONSUME(BinaryLiteral) }, + { ALT: () => this.CONSUME(ByteLiteral) }, + { ALT: () => this.CONSUME(IntegerLiteral) }, + ]); + }); + + private keyword = this.RULE("keyword", () => { + this.OR([ + { ALT: () => this.CONSUME(Keyword) }, + { ALT: () => this.CONSUME(Underscore) }, + { ALT: () => this.CONSUME(Dot) }, + { ALT: () => this.CONSUME(BinaryColon) }, + ]); + }); + + private identifier = this.RULE("identifier", () => { + this.CONSUME(Identifier); + }); + + public parse(script: string) { + const lexed = QLexer.tokenize(script); + this.input = lexed.tokens; + return this.script(); + } +} + +export const QParser = new Parser(); diff --git a/server/src/parser/tokens.ts b/server/src/parser/tokens.ts new file mode 100644 index 00000000..7774cef2 --- /dev/null +++ b/server/src/parser/tokens.ts @@ -0,0 +1,134 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { Lexer, createToken } from "chevrotain"; +import { + DateTimeLiteral, + MiliTimeLiteral, + MinuteLiteral, + NanoTimeLiteral, + SecondLiteral, + TimeStampLiteral, +} from "./literals"; + +export const BlockComment = createToken({ + name: "BlockComment", + pattern: /(?<=(\r?\n|[ \t]*))\/(?:[ \t]*\r?\n)[^\\]*\\?/, + group: Lexer.SKIPPED, +}); + +export const LineComment = createToken({ + name: "LineComment", + pattern: /(?:(?<=\r?\n|[ \t])|(? { - //await this.validateTextDocument(change.document); + // await this.validateTextDocument(change.document); }); this.connection.onNotification("analyzeSourceCode", (config) => this.analyzer.analyzeWorkspace(config) @@ -399,44 +400,28 @@ export default class QLangServer { private async validateTextDocument( textDocument: TextDocument ): Promise { - const settings = await this.getDocumentSettings(textDocument.uri); const text = textDocument.getText(); - const pattern = /\b[A-Z]{2,}\b/g; - let m: RegExpExecArray | null; - - let problems = 0; + QParser.parse(text); const diagnostics: Diagnostic[] = []; - while ((m = pattern.exec(text)) && problems < 1000) { - problems++; + let problems = QParser.errors.length; + if (problems > 1000) { + problems = 1000; + } + for (let i = 0; i < problems; i++) { + const error = QParser.errors[i]; const diagnostic: Diagnostic = { - severity: DiagnosticSeverity.Warning, + severity: DiagnosticSeverity.Error, range: { - start: textDocument.positionAt(m.index), - end: textDocument.positionAt(m.index + m[0].length), + start: textDocument.positionAt(error.token.startOffset), + end: textDocument.positionAt( + error.token.endOffset || error.token.startOffset + ), }, - message: `${m[0]} is all uppercase.`, - source: "ex", + message: error.message, + source: "kdb", }; - diagnostic.relatedInformation = [ - { - location: { - uri: textDocument.uri, - range: Object.assign({}, diagnostic.range), - }, - message: "Spelling matters", - }, - { - location: { - uri: textDocument.uri, - range: Object.assign({}, diagnostic.range), - }, - message: "Particularly for names", - }, - ]; - diagnostics.push(diagnostic); } - this.connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } diff --git a/test/suite/lexer.test.ts b/test/suite/lexer.test.ts new file mode 100644 index 00000000..583e1547 --- /dev/null +++ b/test/suite/lexer.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import * as assert from "assert"; +import { QLexer } from "../../server/src/parser/lexer"; +import { CharLiteral } from "../../server/src/parser/literals"; + +describe("QLexer", () => { + describe("CharLiteral", () => { + it("should tokenize string", () => { + const lexed = QLexer.tokenize('"char"'); + assert.strictEqual(lexed.tokens.length, 1); + assert.strictEqual(lexed.tokens[0].tokenType, CharLiteral); + }); + + it("should tokenize empty string", () => { + const lexed = QLexer.tokenize('""'); + assert.strictEqual(lexed.tokens.length, 1); + assert.strictEqual(lexed.tokens[0].tokenType, CharLiteral); + }); + }); +}); diff --git a/test/suite/parser.test.ts b/test/suite/parser.test.ts new file mode 100644 index 00000000..5d3aa1c3 --- /dev/null +++ b/test/suite/parser.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import * as assert from "assert"; +import { QParser } from "../../server/src/parser/parser"; + +describe("QParser", () => { + describe("comments", () => { + it("should ignore block", () => { + QParser.parse("/\na:\n1\n\\"); + assert.deepEqual(QParser.errors, []); + }); + + it("should ignore line", () => { + QParser.parse("/a:\n"); + assert.deepEqual(QParser.errors, []); + }); + + it("should ignore inline", () => { + QParser.parse("a: 1 /a:\n"); + assert.deepEqual(QParser.errors, []); + }); + + it("should not ignore overloaded slash", () => { + QParser.parse("a: ,/ a:\n"); + assert.strictEqual(QParser.errors.length, 1); + }); + }); + + describe("script", () => { + it("should parse empty", () => { + QParser.parse(""); + assert.deepEqual(QParser.errors, []); + }); + }); + + describe("statement", () => { + it("should parse empty", () => { + QParser.parse(";\n\r\n;"); + assert.deepEqual(QParser.errors, []); + }); + }); + + describe("expression", () => { + describe("identifier", () => { + it("should parse", () => { + QParser.parse("absolute;"); + assert.deepEqual(QParser.errors, []); + }); + + it("should parse namespaced", () => { + QParser.parse(".absolute.value;"); + assert.deepEqual(QParser.errors, []); + }); + }); + + describe("assignment", () => { + it("should parse", () => { + QParser.parse("a:1;"); + assert.deepEqual(QParser.errors, []); + }); + + it("should parse multiple", () => { + QParser.parse("a:b:c:1;"); + assert.deepEqual(QParser.errors, []); + }); + + it("should parse multiline", () => { + QParser.parse("a\n :b:c:\r\n 1;"); + assert.deepEqual(QParser.errors, []); + }); + + it("should parse indexed", () => { + QParser.parse("a[1]:1;"); + assert.deepEqual(QParser.errors, []); + }); + + it("should parse infixed", () => { + QParser.parse("a[1]+:1;"); + assert.deepEqual(QParser.errors, []); + }); + }); + }); +});