Skip to content

Instantly share code, notes, and snippets.

@lujiajing1126
Created December 20, 2021 10:12
Show Gist options
  • Save lujiajing1126/1f13bd5d9a68df634ccc63da2e353297 to your computer and use it in GitHub Desktop.
Save lujiajing1126/1f13bd5d9a68df634ccc63da2e353297 to your computer and use it in GitHub Desktop.
promql parser
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