module.exports = grammar({
name: "rules",
extras: ($) => [/\s/, $.comment],
precedences: ($) => [
[
"member",
"subscript",
"call",
"unary_void",
"binary_times",
"binary_plus",
"binary_relation",
"binary_equality",
"and",
"or",
"ternary",
],
],
rules: {
source_file: ($) =>
seq(
field("version", optional($.rules_version_statement)),
repeat(field("service", $.service_declaration))
),
rules_version_statement: ($) =>
seq("rules_version", "=", field("version", $.string), optional(";")),
service_name_identifier: ($) => /[^\s]+/,
service_declaration: ($) =>
seq(
"service",
field("name", $.service_name_identifier),
"{",
repeat(
choice(
field("function", $.function_declaration),
field("match", $.match_declaration)
)
),
"}"
),
function_declaration: ($) =>
seq(
"function",
field("name", $.identifier),
field("argument", $.function_argument),
"{",
field("body", $.function_body),
"}"
),
function_argument: ($) =>
seq(
"(",
optional(
seq(
field("arg", $.identifier),
repeat(seq(",", field("arg", $.identifier)))
)
),
")"
),
function_body: ($) =>
seq(
repeat(field("statement", $._statement)),
field("return", $.return_statement)
),
return_statement: ($) =>
seq("return", field("expression", $._expression), optional(";")),
match_declaration: ($) =>
seq(
"match",
field("path", $.match_path_parameter),
"{",
repeat1(
choice(
field("function", $.function_declaration),
field("match", $.match_declaration),
field("allow", $.allow_declaration)
)
),
"}"
),
match_path_parameter: ($) =>
repeat1(
choice(
$.path_string,
$.path_capture_string,
$.path_capture_group_string
)
),
path_identifier: ($) => /[a-zA-Z0-9_%\-~&'.:]+/,
path_string: ($) => seq("/", field("path", $.path_identifier)),
path_capture_string: ($) =>
seq("/", "{", field("value", $.identifier), "}"),
path_capture_group_string: ($) =>
seq("/", "{", field("value", $.identifier), "=**", "}"),
allow_operation_literal: ($) =>
choice("read", "get", "list", "write", "update", "delete", "create"),
allow_declaration: ($) =>
seq(
"allow",
field("operation", $.allow_operation_literal),
repeat(seq(",", field("operation", $.allow_operation_literal))),
":",
"if",
field("expression", $._expression),
optional(";")
),
_statement: ($) => $.let_declaration,
let_declaration: ($) =>
seq(
"let",
field("name", $.identifier),
"=",
field("expression", $._expression),
";"
),
_expression: ($) =>
choice(
$.literal,
$.identifier,
$._operator_expression,
$.paran,
$.member_expression,
$.subscript_expression,
$.function_call_expression
),
literal: ($) =>
choice(
$.string,
$.int,
$.float,
$.boolean,
$.list,
$.map,
$.path,
$.null
),
paran: ($) => seq("(", field("expression", $._expression), ")"),
function_call_expression: ($) =>
prec(
"call",
seq(field("name", $.identifier), field("params", $.function_params))
),
function_params: ($) =>
seq(
"(",
optional(
seq(
field("param", $._expression),
repeat(seq(",", field("param", $._expression)))
)
),
")"
),
member_expression: ($) =>
prec.left(
"member",
seq(field("object", $._expression), ".", field("member", $._expression))
),
subscript_expression: ($) =>
prec.left(
"subscript",
seq(
field("object", $._expression),
"[",
field("subscript", $._expression),
"]"
)
),
_operator_expression: ($) =>
choice(
$.unary_expression,
$.binary_expression,
$.ternary_expression,
$.typecheck_expression
),
unary_expression: ($) =>
prec.left(
"unary_void",
seq(
field("operator", choice("!", "-")),
field("expression", $._expression)
)
),
binary_expression: ($) =>
choice(
...[
["&&", "and"],
["||", "or"],
["+", "binary_plus"],
["-", "binary_plus"],
["*", "binary_times"],
["/", "binary_times"],
["%", "binary_times"],
["<", "binary_relation"],
["<=", "binary_relation"],
["==", "binary_equality"],
["!=", "binary_equality"],
[">=", "binary_relation"],
[">", "binary_relation"],
].map(([operator, precedence]) =>
prec.left(
precedence,
seq(
field("left", $._expression),
field("operator", operator),
field("right", $._expression)
)
)
),
...[
["in", "binary_relation"],
].map(([operator, precedence]) =>
prec.left(
precedence,
seq(
field("right", $._expression),
field("operator", operator),
field("left", $._expression)
)
)
)
),
ternary_expression: ($) =>
prec.right(
"ternary",
seq(
field("condition", $._expression),
"?",
field("true", $._expression),
":",
field("false", $._expression)
)
),
typecheck_expression: ($) =>
prec.left(
"binary_relation",
seq(
field("expression", $._expression),
field("operator", "is"),
field("type", $.identifier)
)
),
identifier: ($) => /[a-zA-Z_][a-zA-Z_0-9]*/,
string: ($) =>
token(
choice(
seq('"', /([^"\\\s]*(\\.|))*/, '"'),
seq("'", /([^'\\\s]*(\\.|))*/, "'")
)
),
int: ($) => /[0-9]+/,
float: ($) => choice(/[0-9]+\.[0-9]*/, /\.[0-9]+/),
boolean: ($) => choice("true", "false"),
null: ($) => "null",
list: ($) =>
seq(
"[",
optional(
seq(
field("element", $._expression),
repeat(seq(",", optional(field("element", $._expression))))
)
),
"]"
),
entry: ($) => seq(field("key", $.string), ":", field("value", $._expression)),
map: ($) =>
seq(
"{",
optional(
seq(
field("entry", $.entry),
repeat(seq(",", optional(field("entry", $.entry))))
)
),
"}"
),
path: ($) =>
prec.right(repeat1(choice($.path_string, $.path_reference_string, $.path_bind_string))),
path_reference_string: ($) =>
seq("/", "$", "(", field("value", $._expression), ")"),
path_bind_string: ($) =>
seq("/", "{", field("value", $.identifier), "}"),
comment: ($) => token(choice(/\/\/.*/, /\/\*([^/*]|\*+[^*\/])*\*+\//)),
},
});