tree-sitter-quik
Version:
Quik grammar for tree-sitter
676 lines (621 loc) • 20.8 kB
JavaScript
const PREC = Object.freeze({
NAMED_INFIX: 1,
OPERATOR_INFIX: 2,
IMPLICATION: 3,
OR: 4,
AND: 5,
EQUALITY: 6,
ORDER: 7,
SUM: 8,
PRODUCT: 9,
EXPONENTIATION: 10,
NOT: 11,
PREFIX: 12,
INLINE_EXPRESSION: 13,
TYPE: 14,
CALL: 15,
PIPELINE: 16,
DESTRUCTURING_PATTERN: 17
});
module.exports = grammar({
name: 'quik',
externals: $ => [
$._newline,
$._indent,
$._dedent,
$._string_start,
$._string_content,
$._string_end
],
extras: $ => [$.comment, /\s+/],
word: $ => $._identifier_without_operators,
conflicts: $ => [[$.parameter, $._simple_expression], [$.type_constructor, $.polymorphic_type_constructor], [$.polymorphic_type]],
rules: {
program: $ => seq(optional($.hash_bang_line), optional($._statement_seq)),
_statement: $ => choice($._simple_statement, $._compound_statement),
_statement_seq: $ => choice(
$._statement,
seq($._simple_statement, $._newline, optional($._statement_seq)),
seq($._compound_statement, $._statement_seq)
),
_simple_statement: $ => choice(
alias($.default_import, $.import),
alias($.named_import, $.import),
alias($.external_import, $.import),
alias($.single_named_export, $.export),
alias($.named_export, $.export),
$._simple_expression
),
_compound_statement: $ => choice(
alias($.compound_export, $.export),
$._compound_expression
),
type_pair_or_type_list: $ => commaSep1($._type_pair_or_type),
type_hint_list: $ => commaSep1($.type_hint),
default_import: $ => seq(
'import',
field('type', $.type),
optional(seq(
'from',
field('source', $.string)
))
),
named_import: $ => seq(
'import',
'{',
field('types', $.type_pair_or_type_list),
'}',
'from',
field('source', $.string)
),
external_import: $ => seq(
'import',
'extern',
'{',
field('type_hints', $.type_hint_list),
'}',
'from',
field('source', $.string)
),
single_named_export: $ => seq(
'export',
optional(field('default', 'default')),
field('type', $.type),
optional(seq(
'from',
field('source', $.string)
))
),
named_export: $ => seq(
'export',
'{',
field('types', $.type_pair_or_type_list),
'}',
optional(seq(
'from',
field('source', $.string)
))
),
compound_export: $ => seq(
'export',
optional(field('default', 'default')),
field('declaration', $._compound_declaration)
),
block: $ => seq($._indent, $._expression_seq, $._dedent),
refinement_block: $ => seq(
$._indent,
repeat1($.refinement_clause),
optional(field('default', $.refinement_else_clause)),
$._dedent
),
_refinement_condition: $ => seq('|', $._simple_expression),
refinement_clause: $ => seq(
field('condition', $._refinement_condition),
'=>',
field('body', choice($._simple_expression, $.block))
),
refinement_else_clause: $ => seq(
'|',
'else',
'=>',
field('body', choice($._simple_expression, $.block))
),
type_pair: $ => seq(
$.type,
'=>',
$.type
),
_type_pair_or_type: $ => choice($.type_pair, $.type),
type_arguments: $ => commaSep1($.generic_type),
type_parameters: $ => commaSep1($.polymorphic_type),
intersection_type: $ => prec.right(PREC.TYPE, intersectionType($.generic_type)),
union_type: $ => prec.right(PREC.TYPE, unionType($.generic_type)),
map_type: $ => prec(PREC.TYPE, mapType($.generic_type)),
tuple_type: $ => prec(PREC.TYPE, tupleType($.generic_type)),
list_type: $ => prec(PREC.TYPE, listType($.generic_type)),
refinement_type: $ => prec(PREC.TYPE, refinementType($.generic_type, $.identifier, $._simple_expression)),
type_constructor: $ => prec.right(PREC.TYPE, typeConstructor($.type, $.type_arguments, 'arguments')),
_grouped_type: $ => prec(PREC.TYPE, groupedType($.generic_type)),
_atomic_type: $ => prec(PREC.TYPE, choice(
$.intersection_type,
$.union_type,
$.map_type,
$.tuple_type,
$.list_type,
$.refinement_type,
$.type_constructor,
$._grouped_type
)),
_curried_type: $ => prec.left(curriedType($._curried_type, $._atomic_type)),
generic_type: $ => prec(PREC.TYPE, $._curried_type),
polymorphic_intersection_type: $ => prec.right(PREC.TYPE, intersectionType($.polymorphic_type)),
polymorphic_union_type: $ => prec.right(PREC.TYPE, unionType($.polymorphic_type)),
polymorphic_map_type: $ => prec(PREC.TYPE, mapType($.polymorphic_type)),
polymorphic_tuple_type: $ => prec(PREC.TYPE, tupleType($.polymorphic_type)),
polymorphic_list_type: $ => prec(PREC.TYPE, listType($.polymorphic_type)),
polymorphic_refinement_type: $ => prec(PREC.TYPE, refinementType($.polymorphic_type, $.identifier, $._simple_expression)),
polymorphic_type_constructor: $ => prec.right(PREC.TYPE, typeConstructor($.type, $.type_parameters, 'parameters')),
_grouped_polymorphic_type: $ => prec(PREC.TYPE, groupedType($.polymorphic_type)),
polymorphic_type_condition: $ => prec(PREC.TYPE, seq(
field('interface', $.generic_type),
field('variable', alias($.identifier, $.type_variable))
)),
polymorphic_type_conditions: $ => prec(PREC.TYPE, commaSep1($.polymorphic_type_condition)),
_atomic_polymorphic_type: $ => prec(PREC.TYPE, choice(
$.polymorphic_intersection_type,
$.polymorphic_union_type,
$.polymorphic_map_type,
$.polymorphic_tuple_type,
$.polymorphic_list_type,
$.polymorphic_refinement_type,
$.polymorphic_type_constructor,
$._grouped_polymorphic_type,
alias($.identifier, $.type_variable)
)),
_polymorphic_curried_type: $ => prec.left(curriedType($._polymorphic_curried_type, $._atomic_polymorphic_type)),
polymorphic_type: $ => prec(PREC.TYPE, choice(
$._polymorphic_curried_type,
seq(
field('conditions', $.polymorphic_type_conditions),
'=>',
$._polymorphic_curried_type
)
)),
parameter: $ => prec(PREC.INLINE_EXPRESSION, choice(
field('value', $._literal),
seq(
choice(
field('name', $.identifier),
field('pattern', $._destructuring_pattern)
),
optional(seq(
':',
field('type', $.polymorphic_type)
)),
optional(seq(
'=',
field('default', $._simple_expression)
))
)
)),
rest_parameter: $ => seq(
'...',
field('name', $.identifier),
optional(seq(
':',
field('type', $.polymorphic_type)
)),
optional(seq(
'=',
field('default', $._simple_expression)
))
),
parameters: $ => choice(
seq(
commaSep1($.parameter),
optional(seq(
',',
field('rest', $.rest_parameter)
))
),
field('rest', $.rest_parameter)
),
argument: $ => choice($._expression, '?'),
arguments: $ => commaSep1($.argument),
_destructuring_pattern: $ => prec(PREC.DESTRUCTURING_PATTERN, choice(
alias($.map, $.map_pattern),
// alias($.tuple, $.tuple_pattern),
alias($.list, $.list_pattern)
)),
_expression: $ => choice($._simple_expression, $._compound_expression),
_expression_seq: $ => choice(
$._expression,
seq($._simple_expression, $._newline, optional($._expression_seq)),
seq($._compound_expression, $._expression_seq)
),
expression_pair: $ => seq($._expression, '=>', $._expression),
_compound_expression: $ => choice(
$._compound_declaration,
alias($.compound_function, $.function),
$.if,
$.case,
$.atomic,
alias($.compound_return, $.return),
alias($.compound_hold, $.hold),
alias($.compound_do, $.do),
alias($.compound_include, $.include),
alias($.compound_assignment, $.assignment)
),
_compound_declaration: $ => choice(
alias($.compound_struct, $.struct),
alias($.compound_module, $.module),
alias($.compound_interface, $.interface)
),
compound_struct: $ => seq(
optional(field('mutable', 'mutable')),
'struct',
field('name', $.polymorphic_type),
field('body', $.block)
),
compound_module: $ => seq(
optional(field('mutable', 'mutable')),
'module',
field('name', $.polymorphic_type),
field('body', $.block)
),
interface_block: $ => seq(
$._indent,
choice(
$.pass,
repeat1(choice(
$.type_hint,
alias($.compound_include, $.include),
alias($.simple_include, $.include)
))
),
$._dedent
),
compound_interface: $ => seq(
'interface',
field('name', $.polymorphic_type),
field('body', $.interface_block)
),
compound_function: $ => seq(
'(',
optional(field('parameters', $.parameters)),
')',
choice(
seq(
'=>',
field('body', $.block)
),
field('clauses', $.refinement_block)
)
),
else_clause: $ => seq(
'else',
field('body', $.block)
),
else_if_clause: $ => seq(
'else if',
field('condition', $._simple_expression),
field('body', $.block)
),
else_if_clauses: $ => repeat1($.else_if_clause),
if: $ => seq(
'if',
field('condition', $._simple_expression),
field('body', $.block),
optional(field('else_if_clauses', $.else_if_clauses)),
optional(field('else_clause', $.else_clause))
),
expression_list: $ => commaSep1($._expression),
when_clause: $ => seq(
'when',
field('values', $.expression_list),
field('body', $.block)
),
when_clauses: $ => repeat1($.when_clause),
case: $ => seq(
'case',
field('expression', $._simple_expression),
$._newline,
field('when_clauses', $.when_clauses),
optional(field('else_clause', $.else_clause))
),
atomic: $ => seq('atomic', $.block),
compound_return: $ => seq('return', $._compound_expression),
compound_hold: $ => seq('hold', $._compound_expression),
compound_do: $ => seq('do', $._compound_expression),
compound_include: $ => seq(
'include',
field('declaration', $._compound_declaration)
),
compound_assignment: $ => seq(
field('left', choice($.type_hint, $.identifier, $._destructuring_pattern, $.pipeline)),
'=',
field('right', $._compound_expression)
),
_simple_expression: $ => prec(PREC.INLINE_EXPRESSION, choice(
alias($.simple_return, $.return),
$.pass,
alias($.simple_hold, $.hold),
alias($.simple_do, $.do),
$.retry,
alias($.simple_include, $.include),
alias($.simple_function, $.function),
$._grouped_expression,
$.map,
$.tuple,
$.list,
$.list_comprehension,
$.call,
$.prefix_call,
$.infix_call,
$.pipeline,
alias($.simple_if, $.if),
alias($.simple_assignment, $.assignment),
$.type_assignment,
$.type_hint,
$._literal,
$.generic_type,
$.identifier
)),
simple_return: $ => prec.right(seq('return', optional($._simple_expression))),
pass: $ => 'pass',
simple_hold: $ => prec.right(seq('hold', $._simple_expression)),
simple_do: $ => prec.right(seq('do', $._simple_expression)),
retry: $ => 'retry',
type_list: $ => prec.left(commaSep1($.polymorphic_type)),
simple_include: $ => seq('include', field('types', $.type_list)),
simple_function: $ => seq(
'(',
optional(field('parameters', $.parameters)),
')',
choice(
field('clause', $.refinement_clause),
seq(
'=>',
field('body', $._simple_expression)
)
)
),
_grouped_expression: $ => seq('(', $._simple_expression, ')'),
map: $ => seq(
'{',
commaSep(optional(choice(
$.expression_pair,
alias($.identifier, $.shorthand_pair_identifier),
$.spread
))),
'}'
),
tuple: $ => seq(
'(',
commaSep2(choice(
$._expression,
$.spread
)),
')'
),
list: $ => seq(
'[',
commaSep(choice(
$._expression,
$.spread,
$.range
)),
']'
),
spread: $ => seq('...', $._simple_expression),
range: $ => seq($._expression, '..', $._expression),
list_comprehension: $ => seq(
'[',
field('expression', $._expression),
'|',
field('generators', $.generators),
']'
),
generators: $ => commaSep1($.generator),
generator: $ => seq(
field('variable', $.identifier),
'in',
field('expression', $._simple_expression),
optional(field('condition', $.generator_condition))
),
generator_condition: $ => seq('if', $._simple_expression),
call: $ => prec(PREC.CALL, seq(
$._simple_expression,
'(',
optional(field('arguments', $.arguments)),
')'
)),
prefix_call: $ => prec.right(PREC.PREFIX, seq(
field('operator', $.identifier),
field('expression', $._simple_expression)
)),
infix_call_operator: $ => $._operator,
infix_call: $ => choice(
prec.left(PREC.NOT, seq(field('left', $._simple_expression), field('operator', alias('!', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.EXPONENTIATION, seq(field('left', $._simple_expression), field('operator', alias('^', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.PRODUCT, seq(field('left', $._simple_expression), field('operator', alias('*', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.PRODUCT, seq(field('left', $._simple_expression), field('operator', alias('/', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.PRODUCT, seq(field('left', $._simple_expression), field('operator', alias('%', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.SUM, seq(field('left', $._simple_expression), field('operator', alias('+', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.SUM, seq(field('left', $._simple_expression), field('operator', alias('-', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.ORDER, seq(field('left', $._simple_expression), field('operator', alias('<', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.ORDER, seq(field('left', $._simple_expression), field('operator', alias('<=', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.ORDER, seq(field('left', $._simple_expression), field('operator', alias('>', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.ORDER, seq(field('left', $._simple_expression), field('operator', alias('>=', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.EQUALITY, seq(field('left', $._simple_expression), field('operator', alias('==', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.EQUALITY, seq(field('left', $._simple_expression), field('operator', alias('!=', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.EQUALITY, seq(field('left', $._simple_expression), field('operator', alias('===', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.EQUALITY, seq(field('left', $._simple_expression), field('operator', alias('!==', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.AND, seq(field('left', $._simple_expression), field('operator', alias('&&', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.OR, seq(field('left', $._simple_expression), field('operator', alias('||', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.IMPLICATION, seq(field('left', $._simple_expression), field('operator', alias('==>', $.infix_call_operator)), field('right', $._simple_expression))),
prec.left(PREC.OPERATOR_INFIX, seq(
field('left', $._simple_expression),
field('operator', $.infix_call_operator),
field('right', $._simple_expression))
),
prec.left(PREC.NAMED_INFIX, seq(
field('left', $._simple_expression),
'`',
field('operator', alias($._identifier_without_operators, $.infix_call_operator)),
'`',
field('right', $._simple_expression))
)
),
pipeline: $ => prec.left(PREC.PIPELINE, seq(
$._simple_expression,
optional(field('safe', '&')),
'.',
$._simple_expression
)),
simple_if: $ => prec.right(seq(
'if',
field('condition', $._simple_expression),
'then',
field('body', $._simple_expression),
optional(seq(
'else',
field('else_body', $._simple_expression)
))
)),
simple_assignment: $ => seq(
field('left', choice($.type_hint, $.identifier, $._destructuring_pattern, $.pipeline)),
'=',
field('right', $._simple_expression)
),
type_assignment: $ => seq(
'type',
field('left', $.polymorphic_type),
'=',
field('right', $.polymorphic_type)
),
type_hint: $ => prec.right(seq(
field('identifier', $.identifier),
':',
field('type', $.polymorphic_type)
)),
_literal: $ => choice(
$.number,
$.string,
$.regex,
$.boolean,
$.nil
),
hash_bang_line: $ => /#!.*/,
nil: $ => 'nil',
boolean: $ => choice('false', 'true'),
_decimal: $ => {
const digits = repeat1(/_?[0-9]+/)
const exponent = seq(/e-?/, digits)
return token(choice(
seq(digits, '.', digits, optional(exponent)),
seq(digits, exponent)
))
},
_integer: $ => token(choice(
seq('0x', repeat1(/_?[A-Fa-f0-9]+/)),
seq('0o', repeat1(/_?[0-7]+/)),
seq('0b', repeat1(/_?[0-1]+/)),
seq(repeat1(/_?[0-9]+/))
)),
number: $ => choice($._decimal, $._integer),
type: $ => /_?[A-Z0-9][a-zA-Z0-9]*/,
_identifier_without_operators: $ => /_?[a-z][a-z0-9_]*\??/,
_operator: $ => /(==|[!@#$%^&*|<>~*\-+/]+)=*>?/,
identifier: $ => choice($._operator, $._identifier_without_operators),
string: $ => seq(
$._string_start,
repeat(choice($.interpolation, $.escape_sequence, $._string_content)),
$._string_end
),
interpolation: $ => seq(
'{',
field('expression', $._simple_expression),
'}'
),
escape_sequence: $ => token.immediate(seq(
'\\',
choice(
/[^xu0-7]/,
/[0-7]{1,3}/,
/x[0-9a-fA-F]{2}/,
/u[0-9a-fA-F]{4}/,
/u{[0-9a-fA-F]+}/
)
)),
regex: $ => seq(
'/',
$.regex_pattern,
token.immediate('/'),
optional($.regex_flags)
),
regex_pattern: $ => token.immediate(
repeat1(choice(seq(
'[',
repeat(choice(seq('\\', /./), /[^\]\n\\]/)),
']'
), seq('\\', /./), /[^/\\\[\n]/))
),
regex_flags: $ => token.immediate(/[a-z]+/),
comment: $ => token(seq('#', /.*/))
}
});
function intersectionType(type) {
return seq(field('left', type), '&', field('right', type));
}
function unionType(type) {
return seq(field('left', type), '|', field('right', type));
}
function mapType(type) {
return seq('{', field('key', type), '=>', field('value', type), '}');
}
function tupleType(type) {
return seq('(', commaSep2(type), ')');
}
function listType(type) {
return seq('[', type, ']');
}
function refinementType(type, variable, condition) {
return seq(
'[',
field('variable', variable),
'in',
field('type', type),
'|',
field('condition', condition),
']'
);
}
function typeConstructor(type, arguments, argumentsFieldName) {
return seq(
field('base', type),
optional(seq(
'<',
field(argumentsFieldName, arguments),
'>'
))
);
}
function groupedType(type) {
return seq('(', type, ')');
}
function curriedType(self, atomicType) {
return choice(
atomicType,
seq(atomicType, '->', optional(self))
);
}
function commaSep2(rule) {
return seq(rule, repeat1(seq(',', rule)));
}
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)));
}
function commaSep(rule) {
return optional(commaSep1(rule));
}