module.exports = grammar({
name: "mal",
extras: $ => [
/[ \t\r\n]+/,
$.comment,
],
word: $ => $.identifier,
conflicts: $ => [
[$.association]
],
precedences: $ => [
[ 'binary_exp', 'binary_mul', 'binary_plus', ]
],
rules: {
source_file: $ => repeat($.declaration),
comment: _ => token(prec(0, choice(
seq('//', /[^\r\n\u2028\u2029]*/),
seq(
'/*',
/[^*]*\*+([^/*][^*]*\*+)*/,
'/',
),
))),
declaration: $ => choice(
$.include_declaration,
$.category_declaration,
$.define_declaration,
$.associations_declaration,
),
include_declaration: $ => seq(
'include',
field('file', $.string)
),
category_declaration: $ => seq(
'category',
field('id', $.identifier),
field('meta', repeat($.meta)),
'{',
field('assets', repeat($.asset_declaration)),
'}',
),
asset_declaration: $ => seq(
optional(alias('abstract', 'abstract')),
'asset',
field('id', $.identifier),
field('extends', optional(seq('extends', $.identifier))),
field('meta', repeat($.meta)),
'{',
optional(field('body', $.asset_definition)),
'}',
),
asset_definition: $ => repeat1(choice($.attack_step, $.asset_variable)),
asset_variable: $ => seq(
'let',
field('id', $.identifier),
'=',
field('value', $.asset_expr),
),
attack_step: $ => seq(
field('step_type', $.step_type),
optional(field('causal_mode', $.step_causal_mode)),
field('id', $.identifier),
optional(field('tag', repeat(seq('@', $.identifier)))),
optional(field('cias', seq(
'{',
$.cias,
'}',
))),
optional(field('ttc', $.ttc)),
field('meta', repeat($.meta)),
optional(field('detector', repeat($.detector))),
optional(field('preconditions', $.preconditions)),
optional(field('reaches', $.reaching)),
),
step_type: $ => token(choice(
'|',
'&',
'#',
'E',
'!E',
)),
step_causal_mode: $ => choice(
'action',
'effect',
),
cias: $ => commaSep1($.cia),
detector: $ => seq(
choice('!', token(prec(1, '//!'))),
field('name', $.detector_name),
optional(field('context', $.detector_context)),
optional(field('type', $.identifier)),
optional(field('tp_fp_rate', $.tp_fp_rate)),
),
detector_name: $ => sep1($.identifier, '.'),
detector_context: $ => seq(
'(',
commaSep1($.detector_context_reference),
')',
),
detector_context_reference: $ => seq(
field('ctx_step', $.asset_expr),
field('id', $.identifier),
),
tp_fp_rate: $ => seq(
'[',
choice(
$.tp_fp_pair,
$.tpr_only,
$.fpr_only
),
']',
),
tp_fp_pair: $ => choice(
seq(
field('tpr_key', 'tpr'),
':',
field('tp_rate', $._number),
',',
field('fpr_key', 'fpr'),
':',
field('fp_rate', $._number),
),
seq(
field('fpr_key', 'fpr'),
':',
field('fp_rate', $._number),
',',
field('tpr_key', 'tpr'),
':',
field('tp_rate', $._number),
)
),
tpr_only: $ => seq(
field('tpr_key', 'tpr'),
':',
field('tp_rate', $._number),
),
fpr_only: $ => seq(
field('fpr_key', 'fpr'),
':',
field('fp_rate', $._number),
),
preconditions: $ => seq(
'<-',
field('condition', commaSep1($.asset_expr))
),
reaching: $ => seq(
field('operator', choice('+>', '->')),
field('reaches', commaSep1($.asset_expr))
),
ttc: $ => seq(
'[',
$._ttc_expr,
']',
),
_ttc_expr: $ => choice(
$._ttc_parenthesized,
$._ttc_primary,
$.ttc_binop,
),
_ttc_parenthesized: $ => seq('(', $._ttc_expr, ')'),
_ttc_primary: $ => choice(
$._number,
$.identifier,
$.ttc_distribution,
),
ttc_distribution: $ => seq(
field('id', $.identifier),
'(',
field('values', optional(commaSep1($._number))),
')',
),
ttc_binop: $ => choice(
...[
['+', 'binary_plus'],
['-', 'binary_plus'],
['*', 'binary_mul'],
['/', 'binary_mul'],
['^', 'binary_exp', 'right'],
].map(([operator, precedence, associativity]) =>
(associativity === 'right' ? prec.right : prec.left)(precedence, seq(
field('left', $._ttc_expr),
field('operator', operator),
field('right', $._ttc_expr),
)),
)
),
asset_expr: $ => $._inline_asset_expr,
_inline_asset_expr: $ => choice(
seq('(', $._inline_asset_expr, ')', ),
$._asset_expr_primary,
$.asset_expr_binop,
$.asset_expr_unop,
$.asset_expr_type,
),
_asset_expr_primary: $ => choice(
$.identifier,
$.asset_variable_substitution
),
asset_variable_substitution: $ => seq(
field('id', $.identifier),
'(',
')',
),
asset_expr_type: $ => prec.left('binary_exp', seq(
field('expression', $._inline_asset_expr),
'[',
field('type_id', $.identifier),
']',
)),
asset_expr_binop: $ => choice(
...[
['\\/', 'binary_plus'],
['/\\', 'binary_plus'],
['-', 'binary_plus'],
['.', 'binary_mul'],
].map(([operator, precedence, associativity]) =>
(associativity === 'right' ? prec.right : prec.left)(precedence, seq(
field('left', $._inline_asset_expr),
field('operator', operator),
field('right', $._inline_asset_expr),
)),
)
),
asset_expr_unop: $ => choice(
...[
['*', 'binary_exp'],
].map(([operator, precedence, associativity]) =>
(associativity === 'right' ? prec.right : prec.left)(precedence, seq(
field('expression', $._inline_asset_expr),
field('operator', operator),
)),
)
),
define_declaration: $ => seq(
'#',
field('id', $.identifier),
':',
field('value', $.string)
),
associations_declaration: $ => seq(
'associations',
'{',
repeat($.association),
'}',
),
association: $ => seq(
field('left_id', $.identifier),
'[', field('left_field_id', $.identifier), ']',
field('left_mult', $.multiplicity),
'<--',
field('id', $.identifier),
'-->',
field('right_mult', $.multiplicity),
'[', field('right_field_id', $.identifier), ']',
field('right_id', $.identifier),
field('meta', repeat($.meta)),
),
multiplicity: $ => choice(
$._multiplicity_atom,
$.multiplicity_range,
),
_multiplicity_atom: $ => choice(
$.integer,
$.star,
),
multiplicity_range: $ => seq(
field('start', $._multiplicity_atom),
'..',
field('end', $._multiplicity_atom),
),
meta: $ => seq(
field('id', $.identifier),
'info',
':',
field('info', alias($.string, $.meta_string)),
),
string: _ => token(seq('"', /(?:\\"|[^"])*/, '"')),
_number: $ => choice($.integer, $.float),
integer: _ => token(/[0-9]+/),
float: _ => token(/(:?[0-9]+(:?[.][0-9]*)?|[.][0-9]+)/),
identifier: _ => token(/[a-zA-Z0-9_]+/),
star: _ => token('*'),
cia: _ => token(/[CIA]/)
},
});
function sep1(rule, token) {
return seq(rule, repeat(seq(token, rule)));
}
function commaSep1(rule) {
return sep1(rule, ',');
}