Decodal/editors/tree-sitter-decodal/grammar.js

187 lines
3.7 KiB
JavaScript

const PREC = {
DEFAULT: 1,
PATCH: 2,
AND: 3,
CALL: 7,
PATH: 8,
};
function commaSep(rule) {
return optional(seq(rule, repeat(seq(',', rule)), optional(',')));
}
function semiSep(rule) {
return optional(seq(rule, repeat(seq(';', rule)), optional(';')));
}
module.exports = grammar({
name: 'decodal',
extras: $ => [
/[\s\uFEFF\u2060\u200B]/,
$.comment,
],
word: $ => $.identifier,
conflicts: $ => [
[$._expression, $.parameter],
],
rules: {
source_file: $ => repeat($._statement),
_statement: $ => choice(
seq($.field_definition, optional(';')),
seq($._expression, optional(';')),
),
comment: _ => token(seq('#', /.*/)),
_expression: $ => choice(
$.literal,
$.identifier,
$.regex_literal,
$.comparison_constraint,
$.object,
$.array,
$.let_expression,
$.function_expression,
$.match_expression,
$.import_expression,
$.parenthesized_expression,
$.call_expression,
$.path_expression,
$.binary_expression,
$.default_expression,
),
literal: $ => choice(
$.string,
$.integer,
$.float,
$.boolean,
),
boolean: _ => choice('true', 'false'),
string: _ => token(seq(
'"',
repeat(choice(/[^"\\\n]/, /\\./)),
'"',
)),
integer: _ => token(/[0-9]+/),
float: _ => token(/[0-9]+\.[0-9]+/),
identifier: _ => /[A-Za-z][A-Za-z0-9_]*/,
regex_literal: _ => token(seq(
'/',
repeat1(choice(/[^\/\\\n]/, /\\./)),
'/',
)),
object: $ => seq(
'{',
semiSep($.field_definition),
'}',
),
field_definition: $ => seq(
field('path', $.field_path),
'=',
field('value', $._expression),
),
field_path: $ => prec(10, seq(
$.identifier,
repeat(seq('.', $.identifier)),
)),
array: $ => seq(
'[',
commaSep($._expression),
']',
),
let_expression: $ => seq(
'let',
repeat(seq($.field_definition, ';')),
'in',
field('body', $._expression),
),
function_expression: $ => prec.right(seq(
'(',
commaSep($.parameter),
')',
'=>',
field('body', $._expression),
)),
parameter: $ => seq(
field('name', $.identifier),
optional(seq(':', field('constraint', $._expression))),
),
match_expression: $ => seq(
'match',
field('scrutinee', $._expression),
'{',
semiSep($.match_arm),
'}',
),
match_arm: $ => seq(
field('pattern', choice('_', $._expression)),
':',
field('body', $._expression),
),
import_expression: $ => seq(
'import',
field('specifier', $.string),
),
parenthesized_expression: $ => seq('(', $._expression, ')'),
call_expression: $ => prec.left(PREC.CALL, seq(
field('function', $._expression),
'(',
commaSep($._expression),
')',
)),
path_expression: $ => prec.left(PREC.PATH, seq(
field('object', $._expression),
'.',
field('field', $.identifier),
)),
comparison_constraint: $ => prec(6, seq(
field('operator', choice('>', '>=', '<', '<=')),
field('value', choice($.integer, $.float)),
)),
binary_expression: $ => choice(
prec.left(PREC.AND, seq(
field('left', $._expression),
field('operator', '&'),
field('right', $._expression),
)),
prec.left(PREC.PATCH, seq(
field('left', $._expression),
field('operator', token(prec(2, '//'))),
field('right', $._expression),
)),
),
default_expression: $ => prec.right(PREC.DEFAULT, seq(
field('base', $._expression),
'default',
field('fallback', $._expression),
)),
},
});