296 lines
9.4 KiB
Plaintext
296 lines
9.4 KiB
Plaintext
|
@preprocessor typescript
|
||
|
|
||
|
main -> _ logical_expression _ {% (data) => data[1] %}
|
||
|
|
||
|
# Whitespace: `_` is optional, `__` is mandatory.
|
||
|
_ -> whitespace_character:* {% (data) => data[0].length %}
|
||
|
__ -> whitespace_character:+ {% (data) => data[0].length %}
|
||
|
|
||
|
whitespace_character -> [ \t\n\v\f] {% id %}
|
||
|
|
||
|
# # Numbers
|
||
|
# decimal -> "-":? [0-9]:+ ("." [0-9]:+):? {%
|
||
|
# (data) => parseFloat(
|
||
|
# (data[0] || "") +
|
||
|
# data[1].join("") +
|
||
|
# (data[2] ? "."+data[2][1].join("") : "")
|
||
|
# )
|
||
|
# %}
|
||
|
|
||
|
# Double-quoted string
|
||
|
dqstring -> "\"" dstrchar:* "\"" {% (data) => data[1].join('') %}
|
||
|
sqstring -> "'" sstrchar:* "'" {% (data) => data[1].join('') %}
|
||
|
|
||
|
dstrchar -> [^\\"\n] {% id %}
|
||
|
| "\\" strescape {%
|
||
|
(data) => JSON.parse("\""+data.join("")+"\"")
|
||
|
%}
|
||
|
|
||
|
sstrchar -> [^\\'\n] {% id %}
|
||
|
| "\\" strescape
|
||
|
{% (data) => JSON.parse("\"" + data.join("") + "\"") %}
|
||
|
| "\\'"
|
||
|
{% () => "'" %}
|
||
|
|
||
|
strescape -> ["\\/bfnrt] {% id %}
|
||
|
| "u" [a-fA-F0-9] [a-fA-F0-9] [a-fA-F0-9] [a-fA-F0-9] {%
|
||
|
(data) => data.join('')
|
||
|
%}
|
||
|
|
||
|
logical_expression -> two_op_logical_expression {% id %}
|
||
|
|
||
|
two_op_logical_expression ->
|
||
|
pre_two_op_logical_expression boolean_operator post_one_op_logical_expression {% (data) => ({
|
||
|
type: 'LogicalExpression',
|
||
|
location: {
|
||
|
start: data[0].location.start,
|
||
|
end: data[2].location.end,
|
||
|
},
|
||
|
operator: data[1],
|
||
|
left: data[0],
|
||
|
right: data[2]
|
||
|
}) %}
|
||
|
| pre_two_op_implicit_logical_expression __ post_one_op_implicit_logical_expression {% (data) => ({
|
||
|
type: 'LogicalExpression',
|
||
|
location: {
|
||
|
start: data[0].location.start,
|
||
|
end: data[2].location.end,
|
||
|
},
|
||
|
operator: {
|
||
|
operator: 'AND',
|
||
|
type: 'ImplicitBooleanOperator'
|
||
|
},
|
||
|
left: data[0],
|
||
|
right: data[2]
|
||
|
}) %}
|
||
|
| one_op_logical_expression {% d => d[0] %}
|
||
|
|
||
|
pre_two_op_implicit_logical_expression ->
|
||
|
two_op_logical_expression {% d => d[0] %}
|
||
|
| parentheses_open _ two_op_logical_expression _ parentheses_close {% d => ({location: {start: d[0].location.start, end: d[4].location.start + 1, }, type: 'ParenthesizedExpression', expression: d[2]}) %}
|
||
|
|
||
|
post_one_op_implicit_logical_expression ->
|
||
|
one_op_logical_expression {% d => d[0] %}
|
||
|
| parentheses_open _ one_op_logical_expression _ parentheses_close {% d => ({location: {start: d[0].location.start, end: d[4].location.start + 1, },type: 'ParenthesizedExpression', expression: d[2]}) %}
|
||
|
|
||
|
pre_two_op_logical_expression ->
|
||
|
two_op_logical_expression __ {% d => d[0] %}
|
||
|
| parentheses_open _ two_op_logical_expression _ parentheses_close {% d => ({location: {start: d[0].location.start, end: d[4].location.start + 1, },type: 'ParenthesizedExpression', expression: d[2]}) %}
|
||
|
|
||
|
one_op_logical_expression ->
|
||
|
parentheses_open _ parentheses_close {% d => ({location: {start: d[0].location.start, end: d[2].location.start + 1, },type: 'ParenthesizedExpression', expression: {
|
||
|
type: 'EmptyExpression',
|
||
|
location: {
|
||
|
start: d[0].location.start + 1,
|
||
|
end: d[0].location.start + 1,
|
||
|
},
|
||
|
}}) %}
|
||
|
| parentheses_open _ two_op_logical_expression _ parentheses_close {% d => ({location: {start: d[0].location.start, end: d[4].location.start + 1, },type: 'ParenthesizedExpression', expression: d[2]}) %}
|
||
|
| "NOT" post_boolean_primary {% (data, start) => {
|
||
|
return {
|
||
|
type: 'UnaryOperator',
|
||
|
operator: 'NOT',
|
||
|
operand: data[1],
|
||
|
location: {
|
||
|
start,
|
||
|
end: data[1].location.end,
|
||
|
}
|
||
|
};
|
||
|
} %}
|
||
|
| "-" boolean_primary {% (data, start) => {
|
||
|
return {
|
||
|
type: 'UnaryOperator',
|
||
|
operator: '-',
|
||
|
operand: data[1],
|
||
|
location: {
|
||
|
start,
|
||
|
end: data[1].location.end,
|
||
|
}
|
||
|
};
|
||
|
} %}
|
||
|
| boolean_primary {% d => d[0] %}
|
||
|
|
||
|
post_one_op_logical_expression ->
|
||
|
__ one_op_logical_expression {% d => d[1] %}
|
||
|
| parentheses_open _ one_op_logical_expression _ parentheses_close {% d => ({location: {start: d[0].location, end: d[4].location + 1, },type: 'ParenthesizedExpression', expression: d[2]}) %}
|
||
|
|
||
|
parentheses_open ->
|
||
|
"(" {% (data, start) => ({location: {start}}) %}
|
||
|
|
||
|
parentheses_close ->
|
||
|
")" {% (data, start) => ({location: {start}}) %}
|
||
|
|
||
|
boolean_operator ->
|
||
|
"OR" {% (data, start) => ({location: {start, end: start + 2}, operator: 'OR', type: 'BooleanOperator'}) %}
|
||
|
| "AND" {% (data, start) => ({location: {start, end: start + 3}, operator: 'AND', type: 'BooleanOperator'}) %}
|
||
|
|
||
|
boolean_primary ->
|
||
|
tag_expression {% id %}
|
||
|
|
||
|
post_boolean_primary ->
|
||
|
__ parentheses_open _ two_op_logical_expression _ parentheses_close {% d => ({location: {start: d[1].location.start, end: d[5].location.start + 1, }, type: 'ParenthesizedExpression', expression: d[3]}) %}
|
||
|
| __ boolean_primary {% d => d[1] %}
|
||
|
|
||
|
tag_expression ->
|
||
|
|
||
|
field comparison_operator expression {% (data, start) => {
|
||
|
const field = {
|
||
|
type: 'Field',
|
||
|
name: data[0].name,
|
||
|
path: data[0].name.split('.').filter(Boolean),
|
||
|
quoted: data[0].quoted,
|
||
|
quotes: data[0].quotes,
|
||
|
location: data[0].location,
|
||
|
};
|
||
|
|
||
|
if (!data[0].quotes) {
|
||
|
delete field.quotes;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
location: {
|
||
|
start,
|
||
|
end: data[2].expression.location.end,
|
||
|
},
|
||
|
field,
|
||
|
operator: data[1],
|
||
|
...data[2]
|
||
|
}
|
||
|
} %}
|
||
|
| field comparison_operator {% (data, start) => {
|
||
|
const field = {
|
||
|
type: 'Field',
|
||
|
name: data[0].name,
|
||
|
path: data[0].name.split('.').filter(Boolean),
|
||
|
quoted: data[0].quoted,
|
||
|
quotes: data[0].quotes,
|
||
|
location: data[0].location,
|
||
|
};
|
||
|
|
||
|
if (!data[0].quotes) {
|
||
|
delete field.quotes;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
type: 'Tag',
|
||
|
location: {
|
||
|
start,
|
||
|
end: data[1].location.end,
|
||
|
},
|
||
|
field,
|
||
|
operator: data[1],
|
||
|
expression: {
|
||
|
type: 'EmptyExpression',
|
||
|
location: {
|
||
|
start: data[1].location.end,
|
||
|
end: data[1].location.end,
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
} %}
|
||
|
| expression {% (data, start) => {
|
||
|
return {location: {start, end: data[0].expression.location.end}, field: {type: 'ImplicitField'}, ...data[0]};
|
||
|
} %}
|
||
|
|
||
|
field ->
|
||
|
[_a-zA-Z$] [a-zA-Z\d_$.]:* {% (data, start) => ({type: 'LiteralExpression', name: data[0] + data[1].join(''), quoted: false, location: {start, end: start + (data[0] + data[1].join('')).length}}) %}
|
||
|
| sqstring {% (data, start) => ({type: 'LiteralExpression', name: data[0], quoted: true, quotes: 'single', location: {start, end: start + data[0].length + 2}}) %}
|
||
|
| dqstring {% (data, start) => ({type: 'LiteralExpression', name: data[0], quoted: true, quotes: 'double', location: {start, end: start + data[0].length + 2}}) %}
|
||
|
|
||
|
expression ->
|
||
|
# decimal {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length}, type: 'LiteralExpression', quoted: false, value: Number(data.join(''))}}) %}
|
||
|
regex {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length}, type: 'RegexExpression', value: data.join('')}}) %}
|
||
|
# | range {% (data) => data[0] %}
|
||
|
| unquoted_value {% (data, start, reject) => {
|
||
|
const value = data.join('');
|
||
|
|
||
|
if (data[0] === 'AND' || data[0] === 'OR' || data[0] === 'NOT') {
|
||
|
return reject;
|
||
|
}
|
||
|
|
||
|
let normalizedValue;
|
||
|
|
||
|
if (value === 'true') {
|
||
|
normalizedValue = true;
|
||
|
} else if (value === 'false') {
|
||
|
normalizedValue = false;
|
||
|
} else if (value === 'null') {
|
||
|
normalizedValue = null;
|
||
|
} else {
|
||
|
normalizedValue = value;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
type: 'Tag',
|
||
|
expression: {
|
||
|
location: {
|
||
|
start,
|
||
|
end: start + value.length,
|
||
|
},
|
||
|
type: 'LiteralExpression',
|
||
|
quoted: false,
|
||
|
value: normalizedValue
|
||
|
},
|
||
|
};
|
||
|
} %}
|
||
|
| sqstring {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length + 2}, type: 'LiteralExpression', quoted: true, quotes: 'single', value: data.join('')}}) %}
|
||
|
| dqstring {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length + 2}, type: 'LiteralExpression', quoted: true, quotes: 'double', value: data.join('')}}) %}
|
||
|
|
||
|
# range ->
|
||
|
# range_open decimal " TO " decimal range_close {% (data, start) => {
|
||
|
# return {
|
||
|
# location: {
|
||
|
# start,
|
||
|
# },
|
||
|
# type: 'Tag',
|
||
|
# expression: {
|
||
|
# location: {
|
||
|
# start: data[0].location.start,
|
||
|
# end: data[4].location.start + 1,
|
||
|
# },
|
||
|
# type: 'RangeExpression',
|
||
|
# range: {
|
||
|
# min: data[1],
|
||
|
# minInclusive: data[0].inclusive,
|
||
|
# maxInclusive: data[4].inclusive,
|
||
|
# max: data[3],
|
||
|
# }
|
||
|
# }
|
||
|
# }
|
||
|
# } %}
|
||
|
#
|
||
|
# range_open ->
|
||
|
# "[" {% (data, start) => ({location: {start}, inclusive: true}) %}
|
||
|
# | "{" {% (data, start) => ({location: {start}, inclusive: false}) %}
|
||
|
#
|
||
|
# range_close ->
|
||
|
# "]" {% (data, start) => ({location: {start}, inclusive: true}) %}
|
||
|
# | "}" {% (data, start) => ({location: {start}, inclusive: false}) %}
|
||
|
|
||
|
comparison_operator ->
|
||
|
(
|
||
|
":"
|
||
|
| ":="
|
||
|
| ":>"
|
||
|
| ":<"
|
||
|
| ":>="
|
||
|
| ":<="
|
||
|
) {% (data, start) => ({location: {start, end: start + data[0][0].length}, type: 'ComparisonOperator', operator: data[0][0]}) %}
|
||
|
|
||
|
regex ->
|
||
|
regex_body regex_flags {% d => d.join('') %}
|
||
|
|
||
|
regex_body ->
|
||
|
"/" regex_body_char:* "/" {% (data) => '/' + data[1].join('') + '/' %}
|
||
|
|
||
|
regex_body_char ->
|
||
|
[^\\] {% id %}
|
||
|
| "\\" [^\\] {% d => '\\' + d[1] %}
|
||
|
|
||
|
regex_flags ->
|
||
|
null |
|
||
|
[gmiyusd]:+ {% d => d[0].join('') %}
|
||
|
|
||
|
unquoted_value ->
|
||
|
[a-zA-Z_*?@#$\u0080-\uFFFF0-9] [a-zA-Z\.\-_*?@#$\u0080-\uFFFF0-9]:* {% d => d[0] + d[1].join('') %}
|