import { Diagnostic } from '@codemirror/lint' ; import { SyntaxNode , Tree } from '@lezer/common' ; import { AggregateExpr , And , BinaryExpr , BinModifiers , Bool , Bottomk , CountValues , Eql , EqlSingle , Expr , FunctionCall , FunctionCallArgs , FunctionCallBody , Gte , Gtr , Identifier , LabelMatcher , LabelMatchers , LabelMatchList , Lss , Lte , MatrixSelector , MetricIdentifier , Neq , Or , ParenExpr , Quantile , StepInvariantExpr , SubqueryExpr , Topk , UnaryExpr , Unless , VectorSelector , } from '../grammar/parser.terms' ; import { containsAtLeastOneChild , retrieveAllRecursiveNodes , walkThrough } from './path-finder' ; import { getType } from './type' ; import { buildLabelMatchers } from './matcher' ; import { EditorState } from '@codemirror/state' ; import { syntaxTree } from '@codemirror/language' ; import { getFunction , Matcher , VectorMatchCardinality , ValueType } from '../types' ; import { buildVectorMatching } from './vector' ; export class Parser { private readonly tree : Tree ; private readonly state : EditorState ; private readonly diagnostics : Diagnostic [ ] ; constructor ( state ) { this . tree = syntaxTree ( state ) ; this . state = state ; this . diagnostics = [ ] ; } getDiagnostics ( ) { return this . diagnostics . sort ( ( a , b ) => { return a . from - b . from ; } ) ; } analyze ( ) { this . checkAST ( this . tree . topNode . firstChild ) ; this . diagnoseAllErrorNodes ( ) ; } private diagnoseAllErrorNodes ( ) { const cursor = this . tree . cursor ( ) ; while ( cursor . next ( ) ) { if ( cursor . type . id === 0 && cursor . to !== this . tree . topNode . to ) { const node = cursor . node . parent ; this . diagnostics . push ( { severity : 'error' , message : 'unexpected expression' , from : node ? node . from : cursor . from , to : node ? node . to : cursor . to , } ) ; } } } checkAST ( node ) { if ( ! node ) { return ValueType . none ; } switch ( node . type . id ) { case Expr : return this . checkAST ( node . firstChild ) ; case AggregateExpr : this . checkAggregationExpr ( node ) ; break ; case BinaryExpr : this . checkBinaryExpr ( node ) ; break ; case FunctionCall : this . checkCallFunction ( node ) ; break ; case ParenExpr : this . checkAST ( walkThrough ( node , Expr ) ) ; break ; case UnaryExpr : const unaryExprType = this . checkAST ( walkThrough ( node , Expr ) ) ; if ( unaryExprType !== ValueType . scalar && unaryExprType !== ValueType . vector ) { this . addDiagnostic ( node , `unary expression only allowed on expressions of type scalar or instant vector, got ${ unaryExprType }` ) ; } break ; case SubqueryExpr : const subQueryExprType = this . checkAST ( walkThrough ( node , Expr ) ) ; if ( subQueryExprType !== ValueType . vector ) { this . addDiagnostic ( node , `subquery is only allowed on instant vector, got ${ subQueryExprType } in ${ node . name } instead` ) ; } break ; case MatrixSelector : this . checkAST ( walkThrough ( node , Expr ) ) ; break ; case VectorSelector : this . checkVectorSelector ( node ) ; break ; case StepInvariantExpr : const exprValue = this . checkAST ( walkThrough ( node , Expr ) ) ; if ( exprValue !== ValueType . vector && exprValue !== ValueType . matrix ) { this . addDiagnostic ( node , `@ modifier must be preceded by an instant selector vector or range vector selector or a subquery` ) ; } break ; } return getType ( node ) ; } private checkAggregationExpr ( node ) { const aggregateOp = node . firstChild ?. firstChild ; if ( ! aggregateOp ) { this . addDiagnostic ( node , 'aggregation operator expected in aggregation expression but got nothing' ) ; return ; } const expr = walkThrough ( node , FunctionCallBody , FunctionCallArgs , Expr ) ; if ( ! expr ) { this . addDiagnostic ( node , 'unable to find the parameter for the expression' ) ; return ; } this . expectType ( expr , ValueType . vector , 'aggregation expression' ) ; const params = walkThrough ( node , FunctionCallBody , FunctionCallArgs , FunctionCallArgs , Expr ) ; if ( aggregateOp . type . id === Topk || aggregateOp . type . id === Bottomk || aggregateOp . type . id === Quantile ) { if ( ! params ) { this . addDiagnostic ( node , 'no parameter found' ) ; return ; } this . expectType ( params , ValueType . scalar , 'aggregation parameter' ) ; } if ( aggregateOp . type . id === CountValues ) { if ( ! params ) { this . addDiagnostic ( node , 'no parameter found' ) ; return ; } this . expectType ( params , ValueType . string , 'aggregation parameter' ) ; } } private checkBinaryExpr ( node ) { const lExpr = node . firstChild ; const rExpr = node . lastChild ; if ( ! lExpr || ! rExpr ) { this . addDiagnostic ( node , 'left or right expression is missing in binary expression' ) ; return ; } const lt = this . checkAST ( lExpr ) ; const rt = this . checkAST ( rExpr ) ; const boolModifierUsed = walkThrough ( node , BinModifiers , Bool ) ; const isComparisonOperator = containsAtLeastOneChild ( node , Eql , Neq , Lte , Lss , Gte , Gtr ) ; const isSetOperator = containsAtLeastOneChild ( node , And , Or , Unless ) ; if ( boolModifierUsed ) { if ( ! isComparisonOperator ) { this . addDiagnostic ( node , 'bool modifier can only be used on comparison operators' ) ; } } else { if ( isComparisonOperator && lt === ValueType . scalar && rt === ValueType . scalar ) { this . addDiagnostic ( node , 'comparisons between scalars must use BOOL modifier' ) ; } } const vectorMatching = buildVectorMatching ( this . state , node ) ; if ( vectorMatching !== null && vectorMatching . on ) { for ( const l1 of vectorMatching . matchingLabels ) { for ( const l2 of vectorMatching . include ) { if ( l1 === l2 ) { this . addDiagnostic ( node , `label "${ l1 }" must not occur in ON and GROUP clause at once` ) ; } } } } if ( lt !== ValueType . scalar && lt !== ValueType . vector ) { this . addDiagnostic ( lExpr , 'binary expression must contain only scalar and instant vector types' ) ; } if ( rt !== ValueType . scalar && rt !== ValueType . vector ) { this . addDiagnostic ( rExpr , 'binary expression must contain only scalar and instant vector types' ) ; } if ( ( lt !== ValueType . vector || rt !== ValueType . vector ) && vectorMatching !== null ) { if ( vectorMatching . matchingLabels . length > 0 ) { this . addDiagnostic ( node , 'vector matching only allowed between instant vectors' ) ; } } else { if ( isSetOperator ) { if ( vectorMatching ?. card === VectorMatchCardinality . CardOneToMany || vectorMatching ?. card === VectorMatchCardinality . CardManyToOne ) { this . addDiagnostic ( node , 'no grouping allowed for set operations' ) ; } if ( vectorMatching ?. card !== VectorMatchCardinality . CardManyToMany ) { this . addDiagnostic ( node , 'set operations must always be many-to-many' ) ; } } } if ( ( lt === ValueType . scalar || rt === ValueType . scalar ) && isSetOperator ) { this . addDiagnostic ( node , 'set operator not allowed in binary scalar expression' ) ; } } private checkCallFunction ( node ) { const funcID = node . firstChild ?. firstChild ; if ( ! funcID ) { this . addDiagnostic ( node , 'function not defined' ) ; return ; } const args = retrieveAllRecursiveNodes ( walkThrough ( node , FunctionCallBody ) , FunctionCallArgs , Expr ) ; const funcSignature = getFunction ( funcID . type . id ) ; const nargs = funcSignature . argTypes . length ; if ( funcSignature . variadic === 0 ) { if ( args . length !== nargs ) { this . addDiagnostic ( node , `expected ${ nargs } argument(s) in call to "${ funcSignature . name }", got ${ args . length }` ) ; } } else { const na = nargs - 1 ; if ( na > args . length ) { this . addDiagnostic ( node , `expected at least ${ na } argument(s) in call to "${ funcSignature . name }", got ${ args . length }` ) ; } else { const nargsmax = na + funcSignature . variadic ; if ( funcSignature . variadic > 0 && nargsmax < args . length ) { this . addDiagnostic ( node , `expected at most ${ nargsmax } argument(s) in call to "${ funcSignature . name }", got ${ args . length }` ) ; } } } let j = 0 ; for ( let i = 0 ; i < args . length ; i ++ ) { j = i ; if ( j >= funcSignature . argTypes . length ) { if ( funcSignature . variadic === 0 ) { break ; } j = funcSignature . argTypes . length - 1 ; } this . expectType ( args [ i ] , funcSignature . argTypes [ j ] , `call to function "${ funcSignature . name }"` ) ; } } private checkVectorSelector ( node ) { const labelMatchers = buildLabelMatchers ( retrieveAllRecursiveNodes ( walkThrough ( node , LabelMatchers , LabelMatchList ) , LabelMatchList , LabelMatcher ) , this . state ) ; let vectorSelectorName = '' ; const vectorSelectorNodeName = walkThrough ( node , MetricIdentifier , Identifier ) ; if ( vectorSelectorNodeName ) { vectorSelectorName = this . state . sliceDoc ( vectorSelectorNodeName . from , vectorSelectorNodeName . to ) ; } if ( vectorSelectorName !== '' ) { const labelMatcherMetricName = labelMatchers . find ( ( lm ) => lm . name === '__name__' ) ; if ( labelMatcherMetricName ) { this . addDiagnostic ( node , `metric name must not be set twice: ${ vectorSelectorName } or ${ labelMatcherMetricName . value }` ) ; } labelMatchers . push ( new Matcher ( EqlSingle , '__name__' , vectorSelectorName ) ) ; } const empty = labelMatchers . every ( ( lm ) => lm . matchesEmpty ( ) ) ; if ( empty ) { this . addDiagnostic ( node , 'vector selector must contain at least one non-empty matcher' ) ; } } private expectType ( node , want , context ) { const t = this . checkAST ( node ) ; if ( t !== want ) { this . addDiagnostic ( node , `expected type ${ want } in ${ context }, got ${ t }` ) ; } } private addDiagnostic ( node , msg ) { this . diagnostics . push ( { severity : 'error' , message : msg , from : node . from , to : node . to , } ) ; } }