Skip to content

Commit

Permalink
♻️ Shuffle a bit bundles to have more async
Browse files Browse the repository at this point in the history
  • Loading branch information
dej611 committed Oct 18, 2023
1 parent d7c4b75 commit 121a6df
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 98 deletions.
58 changes: 58 additions & 0 deletions packages/kbn-monaco/src/esql/lib/ast/ast_errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { RecognitionException } from 'antlr4ts';
import { esql_parser } from '../../antlr/esql_parser';
import { getPosition } from './ast_position_utils';

const symbolsLookup: Record<number, string> = Object.entries(esql_parser)
.filter(([k, v]) => typeof v === 'number' && !/RULE_/.test(k) && k.toUpperCase() === k)
.reduce((memo, [k, v]: [string, number]) => {
memo[v] = k;
return memo;
}, {} as Record<number, string>);

export function getExpectedSymbols(expectedTokens: RecognitionException['expectedTokens']) {
const tokenIds = expectedTokens?.toIntegerList().toArray() || [];
const list = [];
for (const tokenId of tokenIds) {
if (tokenId in symbolsLookup) {
list.push(symbolsLookup[tokenId]);
} else if (tokenId === -1) {
list.push('<EOF>');
}
}
return list;
}

export function createError(exception: RecognitionException) {
const token = exception.getOffendingToken();
if (token) {
const expectedSymbols = getExpectedSymbols(exception.expectedTokens);
if (
['ASTERISK', 'UNQUOTED_IDENTIFIER', 'QUOTED_IDENTIFIER'].every(
(s, i) => expectedSymbols[i] === s
)
) {
return {
type: 'error' as const,
text: `Unknown column ${token.text}`,
location: getPosition(token),
};
}
}
return {
type: 'error' as const,
text: token
? `SyntaxError: expected {${getExpectedSymbols(exception.expectedTokens).join(
', '
)}} but found "${token.text}"`
: '',
location: getPosition(token),
};
}
9 changes: 2 additions & 7 deletions packages/kbn-monaco/src/esql/lib/ast/ast_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,8 @@ import {
WhereCommandContext,
} from '../../antlr/esql_parser';
import { esql_parserListener as ESQLParserListener } from '../../antlr/esql_parser_listener';
import {
createCommand,
createFunction,
createOption,
createLiteral,
getPosition,
} from './ast_helpers';
import { createCommand, createFunction, createOption, createLiteral } from './ast_helpers';
import { getPosition } from './ast_position_utils';
import {
collectAllSourceIdentifiers,
collectAllFieldsStatements,
Expand Down
63 changes: 2 additions & 61 deletions packages/kbn-monaco/src/esql/lib/ast/ast_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import type { ParserRuleContext, RecognitionException, Token } from 'antlr4ts';
import type { ParserRuleContext } from 'antlr4ts';
import { ErrorNode } from 'antlr4ts/tree/ErrorNode';
import { TerminalNode } from 'antlr4ts/tree/TerminalNode';
import {
Expand All @@ -16,6 +16,7 @@ import {
IntegerValueContext,
QualifiedIntegerLiteralContext,
} from '../../antlr/esql_parser';
import { getPosition } from './ast_position_utils';
import type {
ESQLCommand,
ESQLLiteral,
Expand All @@ -32,66 +33,6 @@ export function nonNullable<T>(v: T): v is NonNullable<T> {
return v != null;
}

const symbolsLookup: Record<number, string> = Object.entries(esql_parser)
.filter(([k, v]) => typeof v === 'number' && !/RULE_/.test(k) && k.toUpperCase() === k)
.reduce((memo, [k, v]: [string, number]) => {
memo[v] = k;
return memo;
}, {} as Record<number, string>);

export function getExpectedSymbols(expectedTokens: RecognitionException['expectedTokens']) {
const tokenIds = expectedTokens?.toIntegerList().toArray() || [];
const list = [];
for (const tokenId of tokenIds) {
if (tokenId in symbolsLookup) {
list.push(symbolsLookup[tokenId]);
} else if (tokenId === -1) {
list.push('<EOF>');
}
}
return list;
}

export function getPosition(token: Token | undefined, lastToken?: Token | undefined) {
if (!token || token.startIndex < 0) {
return { min: 0, max: 0 };
}
const endFirstToken =
token.stopIndex > -1 ? Math.max(token.stopIndex + 1, token.startIndex) : undefined;
const endLastToken = lastToken?.stopIndex;
return {
min: token.startIndex,
max: endLastToken ?? endFirstToken ?? Infinity,
};
}

export function createError(exception: RecognitionException) {
const token = exception.getOffendingToken();
if (token) {
const expectedSymbols = getExpectedSymbols(exception.expectedTokens);
if (
['ASTERISK', 'UNQUOTED_IDENTIFIER', 'QUOTED_IDENTIFIER'].every(
(s, i) => expectedSymbols[i] === s
)
) {
return {
type: 'error' as const,
text: `Unknown column ${token.text}`,
location: getPosition(token),
};
}
}
return {
type: 'error' as const,
text: token
? `SyntaxError: expected {${getExpectedSymbols(exception.expectedTokens).join(
', '
)}} but found "${token.text}"`
: '',
location: getPosition(token),
};
}

export function createCommand(name: string, ctx: ParserRuleContext): ESQLCommand {
return {
type: 'command',
Expand Down
22 changes: 22 additions & 0 deletions packages/kbn-monaco/src/esql/lib/ast/ast_position_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { Token } from 'antlr4ts';

export function getPosition(token: Token | undefined, lastToken?: Token | undefined) {
if (!token || token.startIndex < 0) {
return { min: 0, max: 0 };
}
const endFirstToken =
token.stopIndex > -1 ? Math.max(token.stopIndex + 1, token.startIndex) : undefined;
const endLastToken = lastToken?.stopIndex;
return {
min: token.startIndex,
max: endLastToken ?? endFirstToken ?? Infinity,
};
}
11 changes: 11 additions & 0 deletions packages/kbn-monaco/src/esql/lib/ast/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { validateAst } from './validation/validation';
export { suggest, getHoverItem, getSignatureHelp } from './autocomplete/autocomplete';
export { AstListener } from './ast_factory';
16 changes: 0 additions & 16 deletions packages/kbn-monaco/src/esql/lib/ast/shared/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,6 @@ export function isIncompleteItem(arg: ESQLAstItem): boolean {
return !arg || (!Array.isArray(arg) && arg.incomplete);
}

// from linear offset to Monaco position
export function offsetToRowColumn(expression: string, offset: number): monaco.Position {
const lines = expression.split(/\n/);
let remainingChars = offset;
let lineNumber = 1;
for (const line of lines) {
if (line.length >= remainingChars) {
return new monaco.Position(lineNumber, remainingChars + 1);
}
remainingChars -= line.length + 1;
lineNumber++;
}

throw new Error('Algorithm failure');
}

// From Monaco position to linear offset
export function monacoPositionToOffset(expression: string, position: monaco.Position): number {
const lines = expression.split(/\n/);
Expand Down
53 changes: 40 additions & 13 deletions packages/kbn-monaco/src/esql/lib/monaco/esql_ast_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,29 @@
import { CharStreams } from 'antlr4ts';
import { monaco } from '../../../monaco_imports';
import { getParser } from '../antlr_facade';
import { AstListener } from '../ast/ast_factory';
import { getHoverItem, getSignatureHelp, suggest } from '../ast/autocomplete/autocomplete';
import { offsetToRowColumn } from '../ast/shared/helpers';
import { ESQLMessage } from '../ast/types';
import { validateAst } from '../ast/validation/validation';
import type { ESQLMessage } from '../ast/types';
import type { ESQLCallbacks } from '../ast/autocomplete/types';
import { ESQLErrorListener } from './esql_error_listener';
import type { AstListener } from '../ast/ast_factory';

const ROOT_STATEMENT = 'singleStatement';

// from linear offset to Monaco position
export function offsetToRowColumn(expression: string, offset: number): monaco.Position {
const lines = expression.split(/\n/);
let remainingChars = offset;
let lineNumber = 1;
for (const line of lines) {
if (line.length >= remainingChars) {
return new monaco.Position(lineNumber, remainingChars + 1);
}
remainingChars -= line.length + 1;
lineNumber++;
}

throw new Error('Algorithm failure');
}

function wrapAsMonacoMessage(type: 'error' | 'warning', code: string, messages: ESQLMessage[]) {
return messages.map((e) => {
const startPosition = e.location
Expand All @@ -40,13 +53,12 @@ function wrapAsMonacoMessage(type: 'error' | 'warning', code: string, messages:
}

export function getLanguageProviders() {
const getAst = (text: string | undefined) => {
const getAst = (text: string | undefined, parseListener: AstListener) => {
if (!text) {
return { ast: [], errors: [] };
}
const inputStream = CharStreams.fromString(text);
const errorListener = new ESQLErrorListener();
const parseListener = new AstListener();
const parser = getParser(inputStream, errorListener, parseListener);

parser[ROOT_STATEMENT]();
Expand All @@ -57,11 +69,15 @@ export function getLanguageProviders() {
errors: [],
};
};

const getWrappedAstFn = (parseListener: AstListener) => (text: string | undefined) =>
getAst(text, parseListener);
return {
// used for debugging purposes only
getAst,
validate: async (code: string, callbacks?: ESQLCallbacks) => {
const { ast } = getAst(code);
const { validateAst, AstListener } = await import('../ast');
const { ast } = await getAst(code, new AstListener());
const { errors, warnings } = await validateAst(ast, callbacks);
const monacoErrors = wrapAsMonacoMessage('error', code, errors);
const monacoWarnings = wrapAsMonacoMessage('warning', code, warnings);
Expand All @@ -70,23 +86,27 @@ export function getLanguageProviders() {
getSignatureHelp: (callbacks?: ESQLCallbacks): monaco.languages.SignatureHelpProvider => {
return {
signatureHelpTriggerCharacters: [' ', '('],
provideSignatureHelp(
async provideSignatureHelp(
model: monaco.editor.ITextModel,
position: monaco.Position,
_token: monaco.CancellationToken,
context: monaco.languages.SignatureHelpContext
) {
return getSignatureHelp(model, position, context, getAst);
const { getSignatureHelp, AstListener } = await import('../ast');
return getSignatureHelp(model, position, context, getWrappedAstFn(new AstListener()));
},
};
},
getHoverProvider: (): monaco.languages.HoverProvider => {
return {
provideHover: (
async provideHover(
model: monaco.editor.ITextModel,
position: monaco.Position,
token: monaco.CancellationToken
) => getHoverItem(model, position, token, getAst),
) {
const { getHoverItem, AstListener } = await import('../ast');
return getHoverItem(model, position, token, getWrappedAstFn(new AstListener()));
},
};
},
getSuggestionProvider: (callbacks?: ESQLCallbacks): monaco.languages.CompletionItemProvider => {
Expand All @@ -97,7 +117,14 @@ export function getLanguageProviders() {
position: monaco.Position,
context: monaco.languages.CompletionContext
): Promise<monaco.languages.CompletionList> {
const suggestionEntries = await suggest(model, position, context, getAst, callbacks);
const { suggest, AstListener } = await import('../ast');
const suggestionEntries = await suggest(
model,
position,
context,
getWrappedAstFn(new AstListener()),
callbacks
);
return {
suggestions: suggestionEntries.map((suggestion) => ({
...suggestion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import type { ANTLRErrorListener, Recognizer, RecognitionException } from 'antlr4ts';
import type { EditorError } from '../../../types';
import { createError } from '../ast/ast_helpers';
import { createError } from '../ast/ast_errors';

export class ESQLErrorListener implements ANTLRErrorListener<any> {
private errors: EditorError[] = [];
Expand Down

0 comments on commit 121a6df

Please sign in to comment.