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), )), }, });