chore: updated grammer for value, added parsetree for finding current context

This commit is contained in:
ahrefabhi 2025-06-24 19:07:26 +05:30
parent e2498863e7
commit 3da7ce5f03
11 changed files with 3742 additions and 384 deletions

View File

@ -14,10 +14,10 @@ import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot'; import { copilot } from '@uiw/codemirror-theme-copilot';
import CodeMirror, { import CodeMirror, {
EditorView, EditorView,
Extension,
keymap, keymap,
Extension,
} from '@uiw/react-codemirror'; } from '@uiw/react-codemirror';
import { Button, Card, Collapse, Popover, Tag } from 'antd'; import { Button, Card, Collapse, Popover, Space, Tag, Typography } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions'; import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion'; import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames'; import cx from 'classnames';
@ -196,7 +196,6 @@ function QuerySearch({
return op.toUpperCase() === 'IN' || op.toUpperCase() === 'NOT IN'; return op.toUpperCase() === 'IN' || op.toUpperCase() === 'NOT IN';
}; };
// Helper function to format value based on operator type and value type
const formatValueForOperator = ( const formatValueForOperator = (
value: string, value: string,
operatorToken: string | undefined, operatorToken: string | undefined,
@ -215,7 +214,10 @@ function QuerySearch({
// If we're already inside bracket list for IN operator and it's a string value // If we're already inside bracket list for IN operator and it's a string value
// just wrap in quotes but not brackets (we're already in brackets) // just wrap in quotes but not brackets (we're already in brackets)
if (type === 'value' || type === 'keyword') { if (
(type === 'value' || type === 'keyword') &&
!/^[a-zA-Z0-9_][a-zA-Z0-9_.\[\]]*$/.test(value)
) {
return wrapStringValueInQuotes(value); return wrapStringValueInQuotes(value);
} }
@ -1054,7 +1056,7 @@ function QuerySearch({
</Collapse> </Collapse>
</Card> </Card>
)} )}
{/*
{queryContext && ( {queryContext && (
<Card size="small" title="Current Context" className="query-context"> <Card size="small" title="Current Context" className="query-context">
<div className="context-details"> <div className="context-details">
@ -1097,7 +1099,7 @@ function QuerySearch({
</Space> </Space>
</div> </div>
</Card> </Card>
)} */} )}
</div> </div>
); );
} }

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -2,25 +2,23 @@
import { ParseTreeListener } from 'antlr4'; import { ParseTreeListener } from 'antlr4';
import { import { QueryContext } from './FilterQueryParser';
AndExpressionContext, import { ExpressionContext } from './FilterQueryParser';
ArrayContext, import { OrExpressionContext } from './FilterQueryParser';
ComparisonContext, import { AndExpressionContext } from './FilterQueryParser';
ExpressionContext, import { UnaryExpressionContext } from './FilterQueryParser';
FullTextContext, import { PrimaryContext } from './FilterQueryParser';
FunctionCallContext, import { ComparisonContext } from './FilterQueryParser';
FunctionParamContext, import { InClauseContext } from './FilterQueryParser';
FunctionParamListContext, import { NotInClauseContext } from './FilterQueryParser';
InClauseContext, import { ValueListContext } from './FilterQueryParser';
KeyContext, import { FullTextContext } from './FilterQueryParser';
NotInClauseContext, import { FunctionCallContext } from './FilterQueryParser';
OrExpressionContext, import { FunctionParamListContext } from './FilterQueryParser';
PrimaryContext, import { FunctionParamContext } from './FilterQueryParser';
QueryContext, import { ArrayContext } from './FilterQueryParser';
UnaryExpressionContext, import { ValueContext } from './FilterQueryParser';
ValueContext, import { KeyContext } from './FilterQueryParser';
ValueListContext,
} from './FilterQueryParser';
/** /**
* This interface defines a complete listener for a parse tree produced by * This interface defines a complete listener for a parse tree produced by
@ -32,199 +30,166 @@ export default class FilterQueryListener extends ParseTreeListener {
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterQuery?: (ctx: QueryContext) => void; enterQuery?: (ctx: QueryContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.query`. * Exit a parse tree produced by `FilterQueryParser.query`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitQuery?: (ctx: QueryContext) => void; exitQuery?: (ctx: QueryContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.expression`. * Enter a parse tree produced by `FilterQueryParser.expression`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterExpression?: (ctx: ExpressionContext) => void; enterExpression?: (ctx: ExpressionContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.expression`. * Exit a parse tree produced by `FilterQueryParser.expression`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitExpression?: (ctx: ExpressionContext) => void; exitExpression?: (ctx: ExpressionContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.orExpression`. * Enter a parse tree produced by `FilterQueryParser.orExpression`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterOrExpression?: (ctx: OrExpressionContext) => void; enterOrExpression?: (ctx: OrExpressionContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.orExpression`. * Exit a parse tree produced by `FilterQueryParser.orExpression`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitOrExpression?: (ctx: OrExpressionContext) => void; exitOrExpression?: (ctx: OrExpressionContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.andExpression`. * Enter a parse tree produced by `FilterQueryParser.andExpression`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterAndExpression?: (ctx: AndExpressionContext) => void; enterAndExpression?: (ctx: AndExpressionContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.andExpression`. * Exit a parse tree produced by `FilterQueryParser.andExpression`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitAndExpression?: (ctx: AndExpressionContext) => void; exitAndExpression?: (ctx: AndExpressionContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.unaryExpression`. * Enter a parse tree produced by `FilterQueryParser.unaryExpression`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterUnaryExpression?: (ctx: UnaryExpressionContext) => void; enterUnaryExpression?: (ctx: UnaryExpressionContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.unaryExpression`. * Exit a parse tree produced by `FilterQueryParser.unaryExpression`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitUnaryExpression?: (ctx: UnaryExpressionContext) => void; exitUnaryExpression?: (ctx: UnaryExpressionContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.primary`. * Enter a parse tree produced by `FilterQueryParser.primary`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterPrimary?: (ctx: PrimaryContext) => void; enterPrimary?: (ctx: PrimaryContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.primary`. * Exit a parse tree produced by `FilterQueryParser.primary`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitPrimary?: (ctx: PrimaryContext) => void; exitPrimary?: (ctx: PrimaryContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.comparison`. * Enter a parse tree produced by `FilterQueryParser.comparison`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterComparison?: (ctx: ComparisonContext) => void; enterComparison?: (ctx: ComparisonContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.comparison`. * Exit a parse tree produced by `FilterQueryParser.comparison`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitComparison?: (ctx: ComparisonContext) => void; exitComparison?: (ctx: ComparisonContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.inClause`. * Enter a parse tree produced by `FilterQueryParser.inClause`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterInClause?: (ctx: InClauseContext) => void; enterInClause?: (ctx: InClauseContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.inClause`. * Exit a parse tree produced by `FilterQueryParser.inClause`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitInClause?: (ctx: InClauseContext) => void; exitInClause?: (ctx: InClauseContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.notInClause`. * Enter a parse tree produced by `FilterQueryParser.notInClause`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterNotInClause?: (ctx: NotInClauseContext) => void; enterNotInClause?: (ctx: NotInClauseContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.notInClause`. * Exit a parse tree produced by `FilterQueryParser.notInClause`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitNotInClause?: (ctx: NotInClauseContext) => void; exitNotInClause?: (ctx: NotInClauseContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.valueList`. * Enter a parse tree produced by `FilterQueryParser.valueList`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterValueList?: (ctx: ValueListContext) => void; enterValueList?: (ctx: ValueListContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.valueList`. * Exit a parse tree produced by `FilterQueryParser.valueList`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitValueList?: (ctx: ValueListContext) => void; exitValueList?: (ctx: ValueListContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.fullText`. * Enter a parse tree produced by `FilterQueryParser.fullText`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterFullText?: (ctx: FullTextContext) => void; enterFullText?: (ctx: FullTextContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.fullText`. * Exit a parse tree produced by `FilterQueryParser.fullText`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitFullText?: (ctx: FullTextContext) => void; exitFullText?: (ctx: FullTextContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.functionCall`. * Enter a parse tree produced by `FilterQueryParser.functionCall`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterFunctionCall?: (ctx: FunctionCallContext) => void; enterFunctionCall?: (ctx: FunctionCallContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.functionCall`. * Exit a parse tree produced by `FilterQueryParser.functionCall`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitFunctionCall?: (ctx: FunctionCallContext) => void; exitFunctionCall?: (ctx: FunctionCallContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.functionParamList`. * Enter a parse tree produced by `FilterQueryParser.functionParamList`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterFunctionParamList?: (ctx: FunctionParamListContext) => void; enterFunctionParamList?: (ctx: FunctionParamListContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.functionParamList`. * Exit a parse tree produced by `FilterQueryParser.functionParamList`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitFunctionParamList?: (ctx: FunctionParamListContext) => void; exitFunctionParamList?: (ctx: FunctionParamListContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.functionParam`. * Enter a parse tree produced by `FilterQueryParser.functionParam`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterFunctionParam?: (ctx: FunctionParamContext) => void; enterFunctionParam?: (ctx: FunctionParamContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.functionParam`. * Exit a parse tree produced by `FilterQueryParser.functionParam`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitFunctionParam?: (ctx: FunctionParamContext) => void; exitFunctionParam?: (ctx: FunctionParamContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.array`. * Enter a parse tree produced by `FilterQueryParser.array`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterArray?: (ctx: ArrayContext) => void; enterArray?: (ctx: ArrayContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.array`. * Exit a parse tree produced by `FilterQueryParser.array`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitArray?: (ctx: ArrayContext) => void; exitArray?: (ctx: ArrayContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.value`. * Enter a parse tree produced by `FilterQueryParser.value`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterValue?: (ctx: ValueContext) => void; enterValue?: (ctx: ValueContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.value`. * Exit a parse tree produced by `FilterQueryParser.value`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
exitValue?: (ctx: ValueContext) => void; exitValue?: (ctx: ValueContext) => void;
/** /**
* Enter a parse tree produced by `FilterQueryParser.key`. * Enter a parse tree produced by `FilterQueryParser.key`.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
enterKey?: (ctx: KeyContext) => void; enterKey?: (ctx: KeyContext) => void;
/** /**
* Exit a parse tree produced by `FilterQueryParser.key`. * Exit a parse tree produced by `FilterQueryParser.key`.
* @param ctx the parse tree * @param ctx the parse tree

View File

@ -1022,17 +1022,15 @@ export default class FilterQueryParser extends Parser {
try { try {
this.state = 202; this.state = 202;
this._errHandler.sync(this); this._errHandler.sync(this);
switch (this._input.LA(1)) { switch (this._interp.adaptivePredict(this._input, 12, this._ctx)) {
case 37: case 1:
this.enterOuterAlt(localctx, 1); this.enterOuterAlt(localctx, 1);
{ {
this.state = 199; this.state = 199;
this.key(); this.key();
} }
break; break;
case 34: case 2:
case 35:
case 36:
this.enterOuterAlt(localctx, 2); this.enterOuterAlt(localctx, 2);
{ {
this.state = 200; this.state = 200;
@ -1046,8 +1044,6 @@ export default class FilterQueryParser extends Parser {
this.array(); this.array();
} }
break; break;
default:
throw new NoViableAltException(this);
} }
} catch (re) { } catch (re) {
if (re instanceof RecognitionException) { if (re instanceof RecognitionException) {
@ -1099,7 +1095,7 @@ export default class FilterQueryParser extends Parser {
{ {
this.state = 208; this.state = 208;
_la = this._input.LA(1); _la = this._input.LA(1);
if (!(((_la - 34) & ~0x1f) === 0 && ((1 << (_la - 34)) & 7) !== 0)) { if (!(((_la - 34) & ~0x1f) === 0 && ((1 << (_la - 34)) & 15) !== 0)) {
this._errHandler.recoverInline(this); this._errHandler.recoverInline(this);
} else { } else {
this._errHandler.reportMatch(this); this._errHandler.reportMatch(this);
@ -1639,7 +1635,7 @@ export default class FilterQueryParser extends Parser {
1, 1,
0, 0,
34, 34,
36, 37,
227, 227,
0, 0,
34, 34,
@ -3809,6 +3805,9 @@ export class ValueContext extends ParserRuleContext {
public BOOL(): TerminalNode { public BOOL(): TerminalNode {
return this.getToken(FilterQueryParser.BOOL, 0); return this.getToken(FilterQueryParser.BOOL, 0);
} }
public KEY(): TerminalNode {
return this.getToken(FilterQueryParser.KEY, 0);
}
public get ruleIndex(): number { public get ruleIndex(): number {
return FilterQueryParser.RULE_value; return FilterQueryParser.RULE_value;
} }

View File

@ -2,25 +2,23 @@
import { ParseTreeVisitor } from 'antlr4'; import { ParseTreeVisitor } from 'antlr4';
import { import { QueryContext } from './FilterQueryParser';
AndExpressionContext, import { ExpressionContext } from './FilterQueryParser';
ArrayContext, import { OrExpressionContext } from './FilterQueryParser';
ComparisonContext, import { AndExpressionContext } from './FilterQueryParser';
ExpressionContext, import { UnaryExpressionContext } from './FilterQueryParser';
FullTextContext, import { PrimaryContext } from './FilterQueryParser';
FunctionCallContext, import { ComparisonContext } from './FilterQueryParser';
FunctionParamContext, import { InClauseContext } from './FilterQueryParser';
FunctionParamListContext, import { NotInClauseContext } from './FilterQueryParser';
InClauseContext, import { ValueListContext } from './FilterQueryParser';
KeyContext, import { FullTextContext } from './FilterQueryParser';
NotInClauseContext, import { FunctionCallContext } from './FilterQueryParser';
OrExpressionContext, import { FunctionParamListContext } from './FilterQueryParser';
PrimaryContext, import { FunctionParamContext } from './FilterQueryParser';
QueryContext, import { ArrayContext } from './FilterQueryParser';
UnaryExpressionContext, import { ValueContext } from './FilterQueryParser';
ValueContext, import { KeyContext } from './FilterQueryParser';
ValueListContext,
} from './FilterQueryParser';
/** /**
* This interface defines a complete generic visitor for a parse tree produced * This interface defines a complete generic visitor for a parse tree produced
@ -38,112 +36,96 @@ export default class FilterQueryVisitor<
* @return the visitor result * @return the visitor result
*/ */
visitQuery?: (ctx: QueryContext) => Result; visitQuery?: (ctx: QueryContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.expression`. * Visit a parse tree produced by `FilterQueryParser.expression`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitExpression?: (ctx: ExpressionContext) => Result; visitExpression?: (ctx: ExpressionContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.orExpression`. * Visit a parse tree produced by `FilterQueryParser.orExpression`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitOrExpression?: (ctx: OrExpressionContext) => Result; visitOrExpression?: (ctx: OrExpressionContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.andExpression`. * Visit a parse tree produced by `FilterQueryParser.andExpression`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitAndExpression?: (ctx: AndExpressionContext) => Result; visitAndExpression?: (ctx: AndExpressionContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.unaryExpression`. * Visit a parse tree produced by `FilterQueryParser.unaryExpression`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitUnaryExpression?: (ctx: UnaryExpressionContext) => Result; visitUnaryExpression?: (ctx: UnaryExpressionContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.primary`. * Visit a parse tree produced by `FilterQueryParser.primary`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitPrimary?: (ctx: PrimaryContext) => Result; visitPrimary?: (ctx: PrimaryContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.comparison`. * Visit a parse tree produced by `FilterQueryParser.comparison`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitComparison?: (ctx: ComparisonContext) => Result; visitComparison?: (ctx: ComparisonContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.inClause`. * Visit a parse tree produced by `FilterQueryParser.inClause`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitInClause?: (ctx: InClauseContext) => Result; visitInClause?: (ctx: InClauseContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.notInClause`. * Visit a parse tree produced by `FilterQueryParser.notInClause`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitNotInClause?: (ctx: NotInClauseContext) => Result; visitNotInClause?: (ctx: NotInClauseContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.valueList`. * Visit a parse tree produced by `FilterQueryParser.valueList`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitValueList?: (ctx: ValueListContext) => Result; visitValueList?: (ctx: ValueListContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.fullText`. * Visit a parse tree produced by `FilterQueryParser.fullText`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitFullText?: (ctx: FullTextContext) => Result; visitFullText?: (ctx: FullTextContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.functionCall`. * Visit a parse tree produced by `FilterQueryParser.functionCall`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitFunctionCall?: (ctx: FunctionCallContext) => Result; visitFunctionCall?: (ctx: FunctionCallContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.functionParamList`. * Visit a parse tree produced by `FilterQueryParser.functionParamList`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitFunctionParamList?: (ctx: FunctionParamListContext) => Result; visitFunctionParamList?: (ctx: FunctionParamListContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.functionParam`. * Visit a parse tree produced by `FilterQueryParser.functionParam`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitFunctionParam?: (ctx: FunctionParamContext) => Result; visitFunctionParam?: (ctx: FunctionParamContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.array`. * Visit a parse tree produced by `FilterQueryParser.array`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitArray?: (ctx: ArrayContext) => Result; visitArray?: (ctx: ArrayContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.value`. * Visit a parse tree produced by `FilterQueryParser.value`.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
visitValue?: (ctx: ValueContext) => Result; visitValue?: (ctx: ValueContext) => Result;
/** /**
* Visit a parse tree produced by `FilterQueryParser.key`. * Visit a parse tree produced by `FilterQueryParser.key`.
* @param ctx the parse tree * @param ctx the parse tree

View File

@ -0,0 +1,94 @@
import FilterQueryLexer from './FilterQueryLexer';
import FilterQueryParser from './FilterQueryParser';
import { ParseTreeWalker, CharStreams, CommonTokenStream, Token } from 'antlr4';
import { isOperatorToken } from 'utils/tokenUtils';
import FilterQueryListener from './FilterQueryListener';
import {
KeyContext,
ValueContext,
ComparisonContext,
} from './FilterQueryParser';
import { IToken } from 'types/antlrQueryTypes';
// 👇 Define the token classification
type TokenClassification = 'Key' | 'Value' | 'Operator';
interface TokenInfo {
text: string;
startIndex: number;
stopIndex: number;
type: TokenClassification;
}
// 👇 Custom listener to walk the parse tree
class TypeTrackingListener implements FilterQueryListener {
public tokens: TokenInfo[] = [];
enterKey(ctx: KeyContext) {
const token = ctx.KEY().symbol;
this.tokens.push({
text: token.text!,
startIndex: token.start,
stopIndex: token.stop,
type: 'Key',
});
}
enterValue(ctx: ValueContext) {
const token = ctx.start;
this.tokens.push({
text: token.text!,
startIndex: token.start,
stopIndex: token.stop,
type: 'Value',
});
}
enterComparison(ctx: ComparisonContext) {
const children = ctx.children || [];
for (const child of children) {
const token = (child as any).symbol;
if (token && isOperatorToken(token.type)) {
this.tokens.push({
text: token.text!,
startIndex: token.start,
stopIndex: token.stop,
type: 'Operator',
});
}
}
}
// Required no-op stubs
enterEveryRule() {}
exitEveryRule() {}
exitKey() {}
exitValue() {}
exitComparison() {}
visitTerminal() {}
visitErrorNode() {}
}
// 👇 Analyze function
export function analyzeQuery(input: string, lastToken: IToken) {
input = input.trim();
const chars = CharStreams.fromString(input);
const lexer = new FilterQueryLexer(chars);
const tokens = new CommonTokenStream(lexer);
const parser = new FilterQueryParser(tokens);
const tree = parser.query();
const listener = new TypeTrackingListener();
ParseTreeWalker.DEFAULT.walk(listener, tree);
const currentToken = listener.tokens.find(
(token) =>
token.text === lastToken.text &&
token.startIndex === lastToken.start &&
token.stopIndex === lastToken.stop,
);
return currentToken;
}

File diff suppressed because one or more lines are too long

View File

@ -93,6 +93,7 @@ value
: QUOTED_TEXT : QUOTED_TEXT
| NUMBER | NUMBER
| BOOL | BOOL
| KEY
; ;
key key

View File

@ -3,6 +3,16 @@
import { CharStreams, CommonTokenStream, Token } from 'antlr4'; import { CharStreams, CommonTokenStream, Token } from 'antlr4';
import FilterQueryLexer from 'parser/FilterQueryLexer'; import FilterQueryLexer from 'parser/FilterQueryLexer';
import { IQueryContext, IQueryPair, IToken } from 'types/antlrQueryTypes'; import { IQueryContext, IQueryPair, IToken } from 'types/antlrQueryTypes';
import { analyzeQuery } from 'parser/analyzeQuery';
import {
isBracketToken,
isConjunctionToken,
isFunctionToken,
isKeyToken,
isMultiValueOperator,
isOperatorToken,
isValueToken,
} from './tokenUtils';
// Function to normalize multiple spaces to single spaces when not in quotes // Function to normalize multiple spaces to single spaces when not in quotes
function normalizeSpaces(query: string): string { function normalizeSpaces(query: string): string {
@ -69,7 +79,8 @@ export function createContext(
// Helper to determine token type for context // Helper to determine token type for context
function determineTokenContext( function determineTokenContext(
tokenType: number, token: IToken,
query: string,
): { ): {
isInKey: boolean; isInKey: boolean;
isInOperator: boolean; isInOperator: boolean;
@ -78,57 +89,49 @@ function determineTokenContext(
isInConjunction: boolean; isInConjunction: boolean;
isInParenthesis: boolean; isInParenthesis: boolean;
} { } {
// Key context let isInKey: boolean = false;
const isInKey = tokenType === FilterQueryLexer.KEY; let isInOperator: boolean = false;
let isInValue: boolean = false;
let isInFunction: boolean = false;
let isInConjunction: boolean = false;
let isInParenthesis: boolean = false;
// Operator context const tokenType = token.type;
const isInOperator = [ const currentTokenContext = analyzeQuery(query, token);
FilterQueryLexer.EQUALS,
FilterQueryLexer.NOT_EQUALS,
FilterQueryLexer.NEQ,
FilterQueryLexer.LT,
FilterQueryLexer.LE,
FilterQueryLexer.GT,
FilterQueryLexer.GE,
FilterQueryLexer.LIKE,
FilterQueryLexer.NOT_LIKE,
FilterQueryLexer.ILIKE,
FilterQueryLexer.NOT_ILIKE,
FilterQueryLexer.BETWEEN,
FilterQueryLexer.EXISTS,
FilterQueryLexer.REGEXP,
FilterQueryLexer.CONTAINS,
FilterQueryLexer.IN,
FilterQueryLexer.NOT,
].includes(tokenType);
// Value context if (!currentTokenContext) {
const isInValue = [ // Key context
FilterQueryLexer.QUOTED_TEXT, isInKey = isKeyToken(tokenType);
FilterQueryLexer.NUMBER,
FilterQueryLexer.BOOL, // Operator context
].includes(tokenType); isInOperator = isOperatorToken(tokenType);
// Value context
isInValue = isValueToken(tokenType);
} else {
switch (currentTokenContext.type) {
case 'Operator':
isInOperator = true;
break;
case 'Value':
isInValue = true;
break;
case 'Key':
isInKey = true;
break;
default:
break;
}
}
// Function context // Function context
const isInFunction = [ isInFunction = isFunctionToken(tokenType);
FilterQueryLexer.HAS,
FilterQueryLexer.HASANY,
FilterQueryLexer.HASALL,
FilterQueryLexer.HASNONE,
].includes(tokenType);
// Conjunction context // Conjunction context
const isInConjunction = [FilterQueryLexer.AND, FilterQueryLexer.OR].includes( isInConjunction = isConjunctionToken(tokenType);
tokenType,
);
// Parenthesis context // Parenthesis context
const isInParenthesis = [ isInParenthesis = isBracketToken(tokenType);
FilterQueryLexer.LPAREN,
FilterQueryLexer.RPAREN,
FilterQueryLexer.LBRACK,
FilterQueryLexer.RBRACK,
].includes(tokenType);
return { return {
isInKey, isInKey,
@ -140,61 +143,6 @@ function determineTokenContext(
}; };
} }
// Helper function to check if a token is an operator
function isOperatorToken(tokenType: number): boolean {
return [
FilterQueryLexer.EQUALS,
FilterQueryLexer.NOT_EQUALS,
FilterQueryLexer.NEQ,
FilterQueryLexer.LT,
FilterQueryLexer.LE,
FilterQueryLexer.GT,
FilterQueryLexer.GE,
FilterQueryLexer.LIKE,
FilterQueryLexer.NOT_LIKE,
FilterQueryLexer.ILIKE,
FilterQueryLexer.NOT_ILIKE,
FilterQueryLexer.BETWEEN,
FilterQueryLexer.EXISTS,
FilterQueryLexer.REGEXP,
FilterQueryLexer.CONTAINS,
FilterQueryLexer.IN,
FilterQueryLexer.NOT,
].includes(tokenType);
}
// Helper function to check if a token is a value
function isValueToken(tokenType: number): boolean {
return [
FilterQueryLexer.QUOTED_TEXT,
FilterQueryLexer.NUMBER,
FilterQueryLexer.BOOL,
].includes(tokenType);
}
// Helper function to check if a token is a conjunction
function isConjunctionToken(tokenType: number): boolean {
return [FilterQueryLexer.AND, FilterQueryLexer.OR].includes(tokenType);
}
// Helper function to check if a token is a bracket
function isBracketToken(tokenType: number): boolean {
return [
FilterQueryLexer.LPAREN,
FilterQueryLexer.RPAREN,
FilterQueryLexer.LBRACK,
FilterQueryLexer.RBRACK,
].includes(tokenType);
}
// Helper function to check if an operator typically uses bracket values (multi-value operators)
function isMultiValueOperator(operatorToken?: string): boolean {
if (!operatorToken) return false;
const upperOp = operatorToken.toUpperCase();
return upperOp === 'IN' || upperOp === 'NOT IN';
}
// Function to determine token context boundaries more precisely // Function to determine token context boundaries more precisely
function determineContextBoundaries( function determineContextBoundaries(
query: string, query: string,
@ -888,7 +836,7 @@ export function getQueryContextAtCursor(
lastTokenBeforeCursor && lastTokenBeforeCursor &&
(isAtSpace || isAfterSpace || isTransitionPoint) (isAtSpace || isAfterSpace || isTransitionPoint)
) { ) {
const lastTokenContext = determineTokenContext(lastTokenBeforeCursor.type); const lastTokenContext = determineTokenContext(lastTokenBeforeCursor, input);
// Apply the context progression logic: key → operator → value → conjunction → key // Apply the context progression logic: key → operator → value → conjunction → key
if (lastTokenContext.isInKey) { if (lastTokenContext.isInKey) {
@ -984,7 +932,7 @@ export function getQueryContextAtCursor(
// FIXED: Consider the case where the cursor is at the end of a token // FIXED: Consider the case where the cursor is at the end of a token
// with no space yet (user is actively typing) // with no space yet (user is actively typing)
if (exactToken && adjustedCursorIndex === exactToken.stop + 1) { if (exactToken && adjustedCursorIndex === exactToken.stop + 1) {
const tokenContext = determineTokenContext(exactToken.type); const tokenContext = determineTokenContext(exactToken, input);
// When the cursor is at the end of a token, return the current token context // When the cursor is at the end of a token, return the current token context
return { return {
@ -1011,7 +959,7 @@ export function getQueryContextAtCursor(
// Regular token-based context detection (when cursor is directly on a token) // Regular token-based context detection (when cursor is directly on a token)
if (exactToken?.channel === 0) { if (exactToken?.channel === 0) {
const tokenContext = determineTokenContext(exactToken.type); const tokenContext = determineTokenContext(exactToken, input);
// Get relevant tokens based on current pair // Get relevant tokens based on current pair
const keyFromPair = currentPair?.key || ''; const keyFromPair = currentPair?.key || '';
@ -1044,7 +992,7 @@ export function getQueryContextAtCursor(
// If we're between tokens but not after a space, use previous token to determine context // If we're between tokens but not after a space, use previous token to determine context
if (previousToken?.channel === 0) { if (previousToken?.channel === 0) {
const prevContext = determineTokenContext(previousToken.type); const prevContext = determineTokenContext(previousToken, input);
// Get relevant tokens based on current pair // Get relevant tokens based on current pair
const keyFromPair = currentPair?.key || ''; const keyFromPair = currentPair?.key || '';
@ -1221,7 +1169,10 @@ export function extractQueryPairs(query: string): IQueryPair[] {
} }
// If token is a KEY, start a new pair // If token is a KEY, start a new pair
if (token.type === FilterQueryLexer.KEY) { if (
token.type === FilterQueryLexer.KEY &&
!(currentPair && currentPair.key)
) {
// If we have an existing incomplete pair, add it to the result // If we have an existing incomplete pair, add it to the result
if (currentPair && currentPair.key) { if (currentPair && currentPair.key) {
queryPairs.push({ queryPairs.push({

View File

@ -0,0 +1,70 @@
import FilterQueryLexer from 'parser/FilterQueryLexer';
export function isKeyToken(tokenType: number): boolean {
return tokenType === FilterQueryLexer.KEY;
}
// Helper function to check if a token is an operator
export function isOperatorToken(tokenType: number): boolean {
return [
FilterQueryLexer.EQUALS,
FilterQueryLexer.NOT_EQUALS,
FilterQueryLexer.NEQ,
FilterQueryLexer.LT,
FilterQueryLexer.LE,
FilterQueryLexer.GT,
FilterQueryLexer.GE,
FilterQueryLexer.LIKE,
FilterQueryLexer.NOT_LIKE,
FilterQueryLexer.ILIKE,
FilterQueryLexer.NOT_ILIKE,
FilterQueryLexer.BETWEEN,
FilterQueryLexer.EXISTS,
FilterQueryLexer.REGEXP,
FilterQueryLexer.CONTAINS,
FilterQueryLexer.IN,
FilterQueryLexer.NOT,
].includes(tokenType);
}
// Helper function to check if a token is a value
export function isValueToken(tokenType: number): boolean {
return [
FilterQueryLexer.QUOTED_TEXT,
FilterQueryLexer.NUMBER,
FilterQueryLexer.BOOL,
FilterQueryLexer.KEY,
].includes(tokenType);
}
// Helper function to check if a token is a conjunction
export function isConjunctionToken(tokenType: number): boolean {
return [FilterQueryLexer.AND, FilterQueryLexer.OR].includes(tokenType);
}
// Helper function to check if a token is a bracket
export function isBracketToken(tokenType: number): boolean {
return [
FilterQueryLexer.LPAREN,
FilterQueryLexer.RPAREN,
FilterQueryLexer.LBRACK,
FilterQueryLexer.RBRACK,
].includes(tokenType);
}
// Helper function to check if an operator typically uses bracket values (multi-value operators)
export function isMultiValueOperator(operatorToken?: string): boolean {
if (!operatorToken) return false;
const upperOp = operatorToken.toUpperCase();
return upperOp === 'IN' || upperOp === 'NOT IN';
}
export function isFunctionToken(tokenType: number): boolean {
return [
FilterQueryLexer.HAS,
FilterQueryLexer.HASANY,
FilterQueryLexer.HASALL,
FilterQueryLexer.HASNONE,
].includes(tokenType);
}