/**
* @namespace kql
* @memberof module:kdljs.parser
*/
const { Lexer } = require('chevrotain')
const { BaseParser } = require('./base.js')
const Tokens = require('./tokens.js')
const tokens = {
defaultMode: 'main',
modes: {
main: [
Tokens.LeftBracket,
Tokens.RightBracket,
Tokens.GreaterOrEqualThan,
Tokens.LessOrEqualThan,
Tokens.GreaterThan,
Tokens.LessThan,
Tokens.Or,
Tokens.AdjacentSibling,
Tokens.Sibling,
Tokens.NotEquals,
Tokens.StartsWith,
Tokens.EndsWith,
Tokens.Contains,
Tokens.Map,
Tokens.TopAccessor,
Tokens.ValAccessor,
Tokens.PropAccessor,
Tokens.Accessor,
Tokens.Comma,
Tokens.WhiteSpace,
Tokens.Boolean,
Tokens.Null,
Tokens.RawString,
Tokens.Integer,
Tokens.Float,
Tokens.Equals,
Tokens.LeftParenthesis,
Tokens.RightParenthesis,
Tokens.OpenQuote,
Tokens.Identifier
],
string: [
Tokens.Unicode,
Tokens.Escape,
Tokens.UnicodeEscape,
Tokens.CloseQuote
]
}
}
/**
* @class
* @extends module:kdljs.parser.base.BaseParser
* @memberof module:kdljs.parser.kql
*/
class KqlParser extends BaseParser {
constructor () {
super(tokens)
/**
* Consume a query
* @method #query
* @memberof module:kdljs.parser.kql.KqlParser
* @return {module:kdljs.query.Query}
*/
this.RULE('query', () => {
const alternatives = [this.SUBRULE(this.selector)]
this.MANY(() => {
this.CONSUME(Tokens.Or)
this.CONSUME1(Tokens.WhiteSpace)
const selector = this.SUBRULE1(this.selector)
alternatives.push(selector)
})
const mapping = this.OPTION(() => {
this.CONSUME(Tokens.Map)
this.CONSUME2(Tokens.WhiteSpace)
return this.SUBRULE(this.mapTuple)
})
this.CONSUME(Tokens.EOF)
return {
alternatives,
mapping
}
})
/**
* Consume a selector
* @method #selector
* @memberof module:kdljs.parser.kql.KqlParser
* @return {module:kdljs.query.Selector}
*/
this.RULE('selector', () => {
const selector = [this.SUBRULE(this.nodeFilter)]
this.OR([
{ ALT: () => this.CONSUME(Tokens.WhiteSpace) },
{ ALT: () => this.CONSUME(Tokens.EOF) }
])
this.MANY(() => {
const operator = this.OPTION(() => {
const operator = this.SUBRULE(this.selectionOperator)
this.CONSUME1(Tokens.WhiteSpace)
return operator
})
const nodeFilter = this.SUBRULE1(this.nodeFilter)
selector.push({ ...nodeFilter, operator })
this.OR1([
{ ALT: () => this.CONSUME2(Tokens.WhiteSpace) },
{ ALT: () => this.CONSUME1(Tokens.EOF) }
])
})
return selector
})
/**
* Consume a nodeFilter
* @method #nodeFilter
* @memberof module:kdljs.parser.kql.KqlParser
* @return {module:kdljs.query.NodeFilter}
*/
this.RULE('nodeFilter', () => {
const matchers = []
this.AT_LEAST_ONE(() => {
matchers.push(this.SUBRULE(this.matcher))
})
return { matchers }
})
/**
* Consume a matcher
* @method #matcher
* @memberof module:kdljs.parser.kql.KqlParser
* @return {module:kdljs.query.Matcher}
*/
this.RULE('matcher', () => {
return this.OR([
{
ALT: () => ({
accessor: { type: 'name' },
operator: '=',
value: this.SUBRULE(this.identifier)
})
},
{
ALT: () => {
this.CONSUME(Tokens.TopAccessor)
this.CONSUME(Tokens.RightParenthesis)
return { accessor: { type: 'top' } }
}
},
{
ALT: () => {
this.CONSUME1(Tokens.LeftParenthesis)
this.CONSUME1(Tokens.RightParenthesis)
return { accessor: { type: 'tag' } }
}
},
{
ALT: () => ({
accessor: { type: 'name' },
operator: '=',
tag: this.SUBRULE(this.tag)
})
},
{ ALT: () => this.SUBRULE(this.accessorMatcher) }
])
})
/**
* Consume a [accessor]-type matcher
* @method #accessorMatcher
* @memberof module:kdljs.parser.kql.KqlParser
* @return {module:kdljs.query.Matcher}
*/
this.RULE('accessorMatcher', () => {
this.CONSUME(Tokens.LeftBracket)
const matcher = this.OPTION(() => {
const accessor = this.SUBRULE(this.accessor)
const matcher = this.OPTION1(() => {
this.CONSUME(Tokens.WhiteSpace)
const operator = this.SUBRULE(this.matcherOperator)
this.CONSUME1(Tokens.WhiteSpace)
const matcher = { accessor, operator }
this.OR([
{ ALT: () => { matcher.value = this.SUBRULE(this.value) } },
{ ALT: () => { matcher.tag = this.SUBRULE(this.tag) } }
])
return matcher
})
return matcher || { accessor }
})
this.CONSUME(Tokens.RightBracket)
return matcher || {}
})
/**
* Consume an accessor
* @method #accessor
* @memberof module:kdljs.parser.kql.KqlParser
* @return {module:kdljs.query.Accessor}
*/
this.RULE('accessor', () => {
return this.OR([
{
ALT: () => {
this.CONSUME(Tokens.ValAccessor)
const parameter = this.OPTION(() => this.SUBRULE(this.value))
this.CONSUME(Tokens.RightParenthesis)
return { type: 'val', parameter }
}
},
{
ALT: () => {
this.CONSUME(Tokens.PropAccessor)
const parameter = this.SUBRULE(this.identifier)
this.CONSUME1(Tokens.RightParenthesis)
return { type: 'prop', parameter }
}
},
{
ALT: () => {
const type = this.CONSUME(Tokens.Accessor).image.slice(0, -1)
this.CONSUME2(Tokens.RightParenthesis)
return { type }
}
},
{
ALT: () => {
const parameter = this.SUBRULE1(this.identifier)
return { type: 'prop', parameter }
}
}
])
})
/**
* Consume an operator within selectors
* @method #selectionOperator
* @memberof module:kdljs.parser.kql.KqlParser
* @return {string}
*/
this.RULE('selectionOperator', () => {
return this.OR([
{ ALT: () => this.CONSUME(Tokens.GreaterThan) },
{ ALT: () => this.CONSUME(Tokens.AdjacentSibling) },
{ ALT: () => this.CONSUME(Tokens.Sibling) }
]).image
})
/**
* Consume an operator within matchers
* @method #matcherOperator
* @memberof module:kdljs.parser.kql.KqlParser
* @return {string}
*/
this.RULE('matcherOperator', () => {
return this.OR([
{ ALT: () => this.CONSUME(Tokens.GreaterThan) },
{ ALT: () => this.CONSUME(Tokens.LessThan) },
{ ALT: () => this.CONSUME(Tokens.GreaterOrEqualThan) },
{ ALT: () => this.CONSUME(Tokens.LessOrEqualThan) },
{ ALT: () => this.CONSUME(Tokens.Equals) },
{ ALT: () => this.CONSUME(Tokens.NotEquals) },
{ ALT: () => this.CONSUME(Tokens.StartsWith) },
{ ALT: () => this.CONSUME(Tokens.EndsWith) },
{ ALT: () => this.CONSUME(Tokens.Contains) }
]).image
})
/**
* Consume a mapping
* @method #mapTuple
* @memberof module:kdljs.parser.kql.KqlParser
* @return {module:kdljs.query.Mapping}
*/
this.RULE('mapTuple', () => {
return this.OR([
{ ALT: () => this.SUBRULE(this.accessor) },
{
ALT: () => {
this.CONSUME(Tokens.LeftParenthesis)
const tuple = [this.SUBRULE1(this.accessor)]
this.MANY(() => {
this.CONSUME(Tokens.Comma)
this.CONSUME(Tokens.WhiteSpace)
tuple.push(this.SUBRULE2(this.accessor))
})
this.CONSUME(Tokens.RightParenthesis)
return tuple
}
}
])
})
this.performSelfAnalysis()
}
}
const lexer = new Lexer(tokens)
/**
* @constant {module:kdljs.parser.kql.KqlParser}
* @memberof module:kdljs.parser.kql
*/
const parser = new KqlParser()
/**
* @typedef ParseResult
* @memberof module:kdljs.parser.kql
* @type {Object}
* @property {Array} errors - Parsing errors
* @property {module:kdljs.queryEngine.Query} output - KQL query
*/
/**
* @function parse
* @memberof module:kdljs.parser.kql
* @param {string} text - Input KQL file (or fragment)
* @return {module:kdljs.parser.kql.ParseResult} Output
*/
module.exports.parse = function parse (text) {
parser.input = lexer.tokenize(text).tokens
const output = parser.query()
return {
output,
errors: parser.errors
}
}
module.exports.lexer = lexer
module.exports.parser = parser
module.exports.KqlParser = KqlParser