Created
December 20, 2021 10:12
-
-
Save lujiajing1126/1f13bd5d9a68df634ccc63da2e353297 to your computer and use it in GitHub Desktop.
promql parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { pipe } from 'fp-ts/lib/function'; | |
import { string as S, parser as P, char as C } from 'parser-ts'; | |
import { | |
Matcher, | |
MatchType, | |
VectorSelector, | |
MatrixSelector, | |
Expr, | |
BinaryOp, | |
BinaryExpr, | |
MultiString, | |
FloatInf, | |
FloatNaN, | |
FloatLiteral, | |
MultiFloat, | |
ParameterRef, | |
} from './model'; | |
import * as N from './number'; | |
import { alts } from './utils'; | |
// ------------------------------------------------------------------------------------- | |
// private parsers | |
// ------------------------------------------------------------------------------------- | |
const parameterRefParser: P.Parser<C.Char, ParameterRef> = pipe( | |
S.string('${{'), | |
P.apSecond(pipe(P.many1(C.alphanum), P.bindTo('ref'))), | |
P.apFirst(S.string('}}')), | |
P.map(a => ({ _tag: 'parameter_ref', refName: a.ref.join('') })), | |
); | |
const multiStringParser: P.Parser<C.Char, MultiString> = P.either( | |
P.surroundedBy(C.char(`"`))(parameterRefParser) as P.Parser< | |
C.Char, | |
MultiString | |
>, | |
() => | |
pipe( | |
S.doubleQuotedString, | |
P.map(a => ({ _tag: 'string_literal', val: a })), | |
), | |
); | |
const firstLetterOfTheIdentity: P.Parser<C.Char, C.Char> = P.sat(c => { | |
return /[a-zA-Z_]/.test(c); | |
}); | |
const labelMatchTypeParser: P.Parser<C.Char, MatchType> = alts( | |
S.string('=~') as P.Parser<C.Char, MatchType>, | |
S.string('!=') as P.Parser<C.Char, MatchType>, | |
S.string('!~') as P.Parser<C.Char, MatchType>, | |
C.char('=') as P.Parser<C.Char, MatchType>, | |
); | |
const binaryOpParser: P.Parser<C.Char, BinaryOp> = alts( | |
C.char('+') as P.Parser<C.Char, BinaryOp>, // ADD | |
S.string('atan2') as P.Parser<C.Char, BinaryOp>, // ATAN2 | |
C.char('/') as P.Parser<C.Char, BinaryOp>, // DIV | |
S.string('==') as P.Parser<C.Char, BinaryOp>, // EQLC | |
S.string('>=') as P.Parser<C.Char, BinaryOp>, // GET | |
C.char('>') as P.Parser<C.Char, BinaryOp>, // GTR | |
S.string('and') as P.Parser<C.Char, BinaryOp>, // LAND | |
S.string('or') as P.Parser<C.Char, BinaryOp>, // LOR | |
S.string('<=') as P.Parser<C.Char, BinaryOp>, // LTE | |
C.char('<') as P.Parser<C.Char, BinaryOp>, // LSS | |
S.string('unless') as P.Parser<C.Char, BinaryOp>, // LUNLESS | |
C.char('%') as P.Parser<C.Char, BinaryOp>, // MOD | |
C.char('*') as P.Parser<C.Char, BinaryOp>, // MUL | |
S.string('!=') as P.Parser<C.Char, BinaryOp>, // NEQ | |
C.char('^') as P.Parser<C.Char, BinaryOp>, // POW | |
C.char('-') as P.Parser<C.Char, BinaryOp>, // POW | |
); | |
const nanParser: P.Parser<C.Char, FloatNaN> = pipe( | |
S.fold([C.oneOf('nN'), C.oneOf('aA'), C.oneOf('nN')]), | |
P.map(() => ({ _tag: 'nan' })), | |
); | |
const infParser: P.Parser<C.Char, FloatInf> = pipe( | |
S.fold([C.oneOf('iI'), C.oneOf('nN'), C.oneOf('fF')]), | |
P.map(() => ({ _tag: 'inf' })), | |
); | |
const floatLiteral: P.Parser<C.Char, FloatLiteral> = pipe( | |
S.fold([ | |
S.maybe(C.oneOf('-+')), | |
pipe( | |
C.char('0'), | |
P.alt(() => S.fold([C.oneOf('123456789'), C.many(C.digit)])), | |
), | |
S.maybe(S.fold([C.char('.'), C.many1(C.digit)])), | |
S.maybe(S.fold([C.oneOf('Ee'), S.maybe(C.oneOf('+-')), C.many1(C.digit)])), | |
]), | |
P.map(val => ({ _tag: 'float_literal', val: parseFloat(val) })), | |
); | |
// ------------------------------------------------------------------------------------- | |
// parsers | |
// ------------------------------------------------------------------------------------- | |
/** | |
* https://prometheus.io/docs/prometheus/latest/querying/basics/#float-literals | |
* [-+]?( | |
* [0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? | |
* | 0[xX][0-9a-fA-F]+ // TBD | |
* | [nN][aA][nN] | |
* | [iI][nN][fF] | |
* ) | |
*/ | |
export const multiFloatParser: P.Parser<C.Char, MultiFloat> = pipe( | |
floatLiteral, | |
P.alt((): P.Parser<C.Char, MultiFloat> => nanParser), // NaN | |
P.alt((): P.Parser<C.Char, MultiFloat> => infParser), // inf | |
P.alt((): P.Parser<C.Char, MultiFloat> => parameterRefParser), | |
); | |
export const DurationParser: P.Parser<C.Char, number> = pipe( | |
N.foldSum([ | |
N.maybeSum( | |
pipe( | |
P.many1Till(C.digit, C.char('y')), | |
P.map(s => parseInt(s.join('')) * 31_536_000), | |
), | |
), | |
N.maybeSum( | |
pipe( | |
P.many1Till(C.digit, C.char('w')), | |
P.map(s => parseInt(s.join('')) * 604_800), | |
), | |
), | |
N.maybeSum( | |
pipe( | |
P.many1Till(C.digit, C.char('d')), | |
P.map(s => parseInt(s.join('')) * 86_400), | |
), | |
), | |
N.maybeSum( | |
pipe( | |
P.many1Till(C.digit, C.char('h')), | |
P.map(s => parseInt(s.join('')) * 3600), | |
), | |
), | |
N.maybeSum( | |
pipe( | |
P.many1Till(C.digit, C.char('m')), | |
P.map(s => parseInt(s.join('')) * 60), | |
), | |
), | |
N.maybeSum( | |
pipe( | |
P.many1Till(C.digit, C.char('s')), | |
P.map(s => parseInt(s.join(''))), | |
), | |
), | |
N.maybeSum( | |
pipe( | |
P.many1Till(C.digit, S.string('ms')), | |
P.map(s => parseInt(s.join('')) / 1000), | |
), | |
), | |
]), | |
P.alt(() => P.of(0)), | |
); | |
// Match the labels name | |
export const LabelNameParser: P.Parser<C.Char, string> = S.fold([ | |
firstLetterOfTheIdentity, | |
C.many(C.alphanum), | |
]); | |
// Match the metric name | |
export const MetricNameParser: P.Parser<C.Char, string> = S.fold([ | |
P.sat(c => /[a-zA-Z_:]/.test(c)), | |
S.many(P.sat(c => /[a-zA-Z0-9_:]/.test(c))), | |
]); | |
export const LabelMatcherListParser: P.Parser< | |
C.Char, | |
ReadonlyArray<Matcher> | |
> = pipe( | |
S.spaces, | |
P.apFirst(C.char('{')), | |
P.apSecond( | |
P.sepBy( | |
// define the separator COMMA | |
pipe(S.spaces, P.apFirst(C.char(',')), P.apFirst(S.spaces)), | |
pipe( | |
LabelNameParser, | |
P.bindTo('name'), | |
P.bind('type', () => labelMatchTypeParser), | |
P.bind('value', () => multiStringParser), | |
), | |
), | |
), | |
P.apFirst(S.spaces), | |
P.apFirst(C.char('}')), | |
P.apFirst(S.spaces), | |
); | |
export const VectorSelectorParser: P.Parser<C.Char, VectorSelector> = pipe( | |
MetricNameParser, | |
P.bindTo('metricIdentifier'), | |
P.bind('labelMatchers', () => P.optional(LabelMatcherListParser)), | |
P.map(a => ({ | |
_tag: 'vector_selector', | |
metricIdentifier: a.metricIdentifier, | |
labelMatchers: a.labelMatchers, | |
})), | |
); | |
export const MatrixSelectorParser: P.Parser<C.Char, MatrixSelector> = pipe( | |
VectorSelectorParser, | |
P.bindTo('vectorSelector'), | |
P.bind('range', () => | |
P.optional(P.between(C.char('['), C.char(']'))(DurationParser)), | |
), | |
P.map(a => ({ | |
_tag: 'matrix_selector', | |
vectorSelector: a.vectorSelector, | |
range: a.range, | |
})), | |
); | |
export const ExprParser: P.Parser<C.Char, Expr> = pipe( | |
P.fail<C.Char, Expr>(), | |
P.alt((): P.Parser<C.Char, Expr> => BinaryExprParser), | |
P.alt((): P.Parser<C.Char, Expr> => MatrixSelectorParser), | |
P.alt((): P.Parser<C.Char, Expr> => multiFloatParser), | |
P.alt((): P.Parser<C.Char, Expr> => VectorSelectorParser), | |
P.alt((): P.Parser<C.Char, Expr> => multiStringParser), | |
); | |
export const BinaryExprParser: P.Parser<C.Char, BinaryExpr> = pipe( | |
MatrixSelectorParser, | |
P.bindTo('l'), | |
P.apFirst(S.spaces), | |
P.bind('op', () => binaryOpParser), | |
P.apFirst(S.spaces), | |
P.bind('r', () => ExprParser), | |
P.map(a => ({ _tag: 'binary_expr', left: a.l, right: a.r, op: a.op })), | |
); | |
export const PromQLParser = pipe(ExprParser, P.apFirst(P.eof())); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment