Files
beginner-friendly-lang/tree-sitter/grammar.js
2025-09-21 13:19:07 +02:00

323 lines
6.9 KiB
JavaScript

/**
* @file Parser for the Crepuscular(ray) functional programming language
* @author Alexander Nutz <alexander.nutz@vxcc.dev>
* @license MIT
*/
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check
module.exports = grammar({
name: "crepuscular",
reserved: {
toplevel_kw: $ =>
['type', 'with', 'extensible', 'extend', 'union', 'def'],
},
extras: ($) => [
/\s/, // whitespace
$.comment,
],
word: $ => $._identifier_tok,
precedences: _ => [
[
"exponent",
"multiplication",
"negate",
"addition",
"concat",
"with",
"equal",
"if",
"let",
"new_match_arm",
"match_arm",
"tag",
],
],
conflicts: $ => [
[$.match_expr, $.match_expr],
],
rules: {
source_file: $ => repeat($._definition),
_identifier_tok: $ => token(/[a-zA-Z_]+[a-zA-Z0-9_]*/),
identifier: $ => reserved('toplevel_kw', $._identifier_tok),
path: $ => prec.left(seq($.identifier, repeat(seq('.', $.identifier)))),
comment: $ =>
token(seq("#", /.*/)),
_definition: $ => choice(
$.full_partial_type_definition,
$.type_definition,
$.extensible_union,
$.extend_decl,
$.def,
),
extensible_union: $ => seq(
'extensible', 'union', $.path),
extend_decl: $ => seq(
'extend', $.path, 'with', $.tag, $._type),
full_partial_type_definition: $ => seq(
"type",
"?", $.path,
"=",
$._type
),
type_definition: $ => seq(
"type",
repeat($.identifier),
$.path,
"=",
$._type
),
_type_atom: $ => choice(
$.just_type,
$.partial_type,
seq('(', $._type, ')'),
$.record_type,
),
_type_non_fn: $ => choice(
$._type_atom,
$.tagged_type,
$.union_type,
$.partial_union_type,
$.parametrized_type,
$.with_type,
$.recursive_type,
),
_type: $ => choice(
$._type_non_fn,
$.fn_type,
),
union_type: $ => prec.left(1,
seq($._type, '|', $._type)),
partial_union_type: $ => prec.left(1,
seq($._type, '|', '...', $.partial_type)),
tag: $ => new RustRegex("'(?:[a-zA-Z_][a-zA-Z0-9_]*(?:[.][a-zA-Z_0-9]+)*)"),
tagged_type: $ => prec.right(3,
seq($.tag, optional(
choice(
$._type_atom,
$.parametrized_type)))),
multi_type_parameters: $ => seq('[', $._type, repeat(seq(',', $._type)), ']'),
parametrized_type: $ => prec.left(4, seq(
choice(
$.multi_type_parameters,
$._type_atom
),
repeat1($.path)
)),
with_type: $ => seq('with', $.identifier, repeat(seq(',', $.identifier)), ':', $._type),
recursive_type: $ => seq('&', $.identifier, $._type),
partial_type: $ => seq('?', $.identifier),
fn_type: $ => prec.left(-10,
seq($._type, '->', $._type)),
just_type: $ => prec(-1, $.path),
record_type_field: $ => seq($.identifier, ':', $._type),
record_type: $ => seq(
'{',
repeat(seq($.record_type_field, ',')),
optional(choice(
$.record_type_field,
seq('...', $.partial_type),
)),
'}'),
escape_sequence: $ =>
token.immediate(
seq('\\', /[tbrnf0"'\\]/)),
char_middle: $ => /./,
string_middle: $ => /[^\\"]+/,
char_literal: $ =>
seq('\'', choice($.escape_sequence, $.char_middle), '\''),
string_literal: $ =>
seq('"', repeat(choice($.escape_sequence, $.string_middle)), '"'),
num_literal: $ =>
seq(
choice(
/[0-9]+/,
/\-[0-9]+/
),
optional(token.immediate(/[.][0-9]+/))
),
list_expression: $ =>
seq(
'[',
repeat(seq($.expression, ',')),
optional($.expression),
']'),
field_access: $ => prec.left(
seq($._atom, ':', $.identifier)),
function_call: $ => prec.left(1,
seq($._atom, '(',
repeat(seq($.expression, ',')), optional($.expression),
')')),
ident_expr: $ => $.identifier,
record_expr_field: $ =>
seq($.identifier, ':', $.expression),
record_expr: $ => seq(
'{',
repeat(seq($.record_expr_field, ',')),
optional($.record_expr_field),
'}'),
_atom: $ => choice(
prec(0, seq('(', $.expression, ')')),
$.ident_expr,
$.char_literal,
$.string_literal,
$.num_literal,
$.list_expression,
$.field_access,
$.function_call,
$.record_expr,
),
let_binding: $ => prec("let", seq(
'let',
$.identifier,
'=',
$.expression,
optional('in'),
$.expression,
)),
await_binding: $ => prec("let", seq(
'await',
$.identifier,
'=',
$.expression,
optional('in'),
$.expression,
)),
type_downcast: $ => seq(
$._atom,
'::',
$._type,
),
lambda: $ => prec.right(4, seq(
$.identifier,
optional(seq(':', $._type_non_fn)),
'->',
$.expression
)),
with_expr: $ => prec.left("with",
seq($.expression, 'with', $._atom)),
and_expr: $ => prec.left("with",
seq($.expression, 'and', $._atom)),
if_expr: $ => prec("if",
seq('if', $.expression, 'then', $.expression, 'else', $.expression)),
sub_expr: $ => prec.left("addition",
seq($.expression, '-', $.expression)),
add_expr: $ => prec.left("addition",
seq($.expression, '+', $.expression)),
divide_expr: $ => prec.left("multiplication",
seq($.expression, '/', $.expression)),
multiply_expr: $ => prec.left("multiplication",
seq($.expression, '*', $.expression)),
equal_expr: $ => prec.left("equal",
seq($.expression, '=', $.expression)),
concat_expr: $ => prec.left("concat",
seq($.expression, '++', $.expression)),
compose_expr: $ => prec.left("concat",
seq($.expression, '=>', $.expression)),
exponent_expr: $ => prec.left("exponent",
seq($.expression, '^', $._atom)),
match_arm: $ => prec("match_arm",
seq(
field('cases', seq($._atom, repeat(seq('|', $._atom)))),
'->', $.expression)),
match_expr: $ =>
seq('match', $.expression, 'with',
$.match_arm,
prec("new_match_arm", repeat(seq('|', $.match_arm)))),
negate_expr: $ => prec.right("negate",
seq('-', $.expression)),
tag_expr: $ => prec.right("tag",
seq($.tag, $.expression)),
expression: $ => choice(
$._atom,
$.let_binding,
$.await_binding,
$.type_downcast,
$.lambda,
$.with_expr,
$.and_expr,
$.if_expr,
$.match_expr,
$.tag_expr,
$.add_expr,
$.sub_expr,
$.divide_expr,
$.multiply_expr,
$.concat_expr,
$.compose_expr,
$.equal_expr,
$.exponent_expr,
$.negate_expr,
),
def: $ => seq(
'def', $.path,
choice(
seq(':', $._type),
seq(
optional(seq(':', $._type)),
seq('=', $.expression),
)
),
),
}
});