Skip to content

Commit

Permalink
Extended parsing of preparser options (CYPHER ...) + color some new o…
Browse files Browse the repository at this point in the history
…ptions(#301)
  • Loading branch information
anderson4j authored Dec 18, 2024
1 parent d329252 commit 84a12fc
Show file tree
Hide file tree
Showing 12 changed files with 425 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-tools-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@neo4j-cypher/language-support': patch
---

Added parsing of CYPHER <version> and CYPHER <optionName> = <value>
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ serverCompletionRule: SERVER;

// This rule overrides the identifiers adding EXPLAIN, PROFILE, etc
unescapedLabelSymbolicNameString:
preparserOption
preparserKeyword
| HISTORY
| CLEAR
| PARAM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ EXPLAIN:
E X P L A I N;

PROFILE:
P R O F I L E;
P R O F I L E;

CYPHER:
C Y P H E R;
22 changes: 20 additions & 2 deletions packages/language-support/src/antlr-grammar/CypherPreParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,25 @@ import Cypher5Parser;
options { tokenVocab = CypherPreLexer; }

preparsedStatement:
preparserOption? statement;
preparserOption* statement;

preparserKeyword:
EXPLAIN | PROFILE | CYPHER;

preparserOption:
EXPLAIN | PROFILE;
EXPLAIN | PROFILE | cypherOptions;

cypherOptions:
CYPHER cypherVersion? cypherOption*;

cypherOption:
cypherOptionName EQ cypherOptionValue;

cypherOptionValue:
(IDENTIFIER | numberLiteral);

cypherOptionName:
IDENTIFIER;

cypherVersion:
UNSIGNED_DECIMAL_INTEGER;
3 changes: 3 additions & 0 deletions packages/language-support/src/lexerSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export enum CypherTokenType {
punctuation = 'punctuation',
none = 'none',
consoleCommand = 'consoleCommand',
settingValue = 'settingValue',
setting = 'setting',
}

export const lexerOperators = [
Expand Down Expand Up @@ -365,6 +367,7 @@ export const lexerKeywords = [
// Preparser tokens
CypherLexer.EXPLAIN,
CypherLexer.PROFILE,
CypherLexer.CYPHER,
];

export const lexerConsoleCmds = [
Expand Down
14 changes: 13 additions & 1 deletion packages/language-support/src/syntaxColouring/syntaxColouring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
ArrowLineContext,
BooleanLiteralContext,
ConsoleCommandContext,
CypherOptionNameContext,
CypherOptionValueContext,
FunctionNameContext,
KeywordLiteralContext,
LabelNameContext,
Expand Down Expand Up @@ -74,6 +76,8 @@ export function mapCypherToSemanticTokenIndex(
[CypherTokenType.paramDollar]: SemanticTokenTypes.namespace,
[CypherTokenType.paramValue]: SemanticTokenTypes.parameter,
[CypherTokenType.property]: SemanticTokenTypes.property,
[CypherTokenType.setting]: SemanticTokenTypes.enum,
[CypherTokenType.settingValue]: SemanticTokenTypes.enumMember,
[CypherTokenType.label]: SemanticTokenTypes.type,
[CypherTokenType.variable]: SemanticTokenTypes.variable,
[CypherTokenType.symbolicName]: SemanticTokenTypes.variable,
Expand Down Expand Up @@ -110,6 +114,14 @@ class SyntaxHighlighter extends CypherParserListener {
}
}

exitCypherOptionValue = (ctx: CypherOptionValueContext) => {
this.addToken(ctx.start, CypherTokenType.settingValue, ctx.getText());
};

exitCypherOptionName = (ctx: CypherOptionNameContext) => {
this.addToken(ctx.start, CypherTokenType.setting, ctx.getText());
};

exitLabelName = (ctx: LabelNameContext) => {
this.addToken(ctx.start, CypherTokenType.label, ctx.getText());
};
Expand Down Expand Up @@ -338,7 +350,7 @@ export function applySyntaxColouring(
/* Get a second pass at the colouring correcting the colours
using structural information from the parsing tree
This allows to correclty colour things that are
This allows to correctly colour things that are
recognized as keywords by the lexer in positions
where they are not keywords (e.g. MATCH (MATCH: MATCH))
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,27 @@ describe('MATCH auto-completion', () => {
});
});

test('Correctly completes even with preparser options', () => {
const query = 'CYPHER 25 runtime=pipelined MATCH (n:P';

testCompletions({
query,
dbSchema: { labels: ['Cat', 'Person', 'Dog'] },
expected: [{ label: 'Person', kind: CompletionItemKind.TypeParameter }],
});

const query2 = 'CYPHER 25 runtime=pipelined MATCH (n:Person)-[:W';

testCompletions({
query: query2,
dbSchema: {
labels: ['Cat', 'Person', 'Dog'],
relationshipTypes: ['WALKS', 'OWNS'],
},
expected: [{ label: 'WALKS', kind: CompletionItemKind.TypeParameter }],
});
});

test("Doesn't complete label before : is entered", () => {
const query = 'MATCH (n';

Expand Down
171 changes: 171 additions & 0 deletions packages/language-support/src/tests/syntaxColouring/preparser.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,177 @@
import { applySyntaxColouring } from '../../syntaxColouring/syntaxColouring';

describe('Preparser syntax colouring', () => {
test('Correctly colours cypher versions', () => {
const query = 'CYPHER 25 MATCH (m) RETURN m';
expect(applySyntaxColouring(query)).toEqual([
{
bracketInfo: undefined,
length: 6,
position: {
line: 0,
startCharacter: 0,
startOffset: 0,
},
token: 'CYPHER',
tokenType: 'keyword',
},
{
bracketInfo: undefined,
length: 2,
position: {
line: 0,
startCharacter: 7,
startOffset: 7,
},
token: '25',
tokenType: 'numberLiteral',
},
{
bracketInfo: undefined,
length: 5,
position: {
line: 0,
startCharacter: 10,
startOffset: 10,
},
token: 'MATCH',
tokenType: 'keyword',
},
{
bracketInfo: {
bracketLevel: 0,
bracketType: 'parenthesis',
},
length: 1,
position: {
line: 0,
startCharacter: 16,
startOffset: 16,
},
token: '(',
tokenType: 'bracket',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 0,
startCharacter: 17,
startOffset: 17,
},
token: 'm',
tokenType: 'variable',
},
{
bracketInfo: {
bracketLevel: 0,
bracketType: 'parenthesis',
},
length: 1,
position: {
line: 0,
startCharacter: 18,
startOffset: 18,
},
token: ')',
tokenType: 'bracket',
},
{
bracketInfo: undefined,
length: 6,
position: {
line: 0,
startCharacter: 20,
startOffset: 20,
},
token: 'RETURN',
tokenType: 'keyword',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 0,
startCharacter: 27,
startOffset: 27,
},
token: 'm',
tokenType: 'variable',
},
]);
});
test('Correctly colours CYPHER <setting> = <setting-value> options', () => {
const query = 'CYPHER runtime = slotted RETURN ""';
expect(applySyntaxColouring(query)).toEqual([
{
bracketInfo: undefined,
length: 6,
position: {
line: 0,
startCharacter: 0,
startOffset: 0,
},
token: 'CYPHER',
tokenType: 'keyword',
},
{
bracketInfo: undefined,
length: 7,
position: {
line: 0,
startCharacter: 7,
startOffset: 7,
},
token: 'runtime',
tokenType: 'setting',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 0,
startCharacter: 15,
startOffset: 15,
},
token: '=',
tokenType: 'operator',
},
{
bracketInfo: undefined,
length: 7,
position: {
line: 0,
startCharacter: 17,
startOffset: 17,
},
token: 'slotted',
tokenType: 'settingValue',
},
{
bracketInfo: undefined,
length: 6,
position: {
line: 0,
startCharacter: 25,
startOffset: 25,
},
token: 'RETURN',
tokenType: 'keyword',
},
{
bracketInfo: undefined,
length: 2,
position: {
line: 0,
startCharacter: 32,
startOffset: 32,
},
token: '""',
tokenType: 'stringLiteral',
},
]);
});

test('Correctly colours PROFILE', () => {
const query = 'PROFILE MATCH (n) RETURN n';

Expand Down
Loading

0 comments on commit 84a12fc

Please sign in to comment.