mirror of https://github.com/Wilfred/difftastic/
398 lines
13 KiB
JavaScript
398 lines
13 KiB
JavaScript
/*
|
|
* grammar.js
|
|
* Copyright (C) 2021 Stephan Seitz <stephan.seitz@fau.de>
|
|
* Adapted from tree-sitter-clojure
|
|
*
|
|
* Distributed under terms of the MIT license.
|
|
*/
|
|
|
|
const clojure = require("tree-sitter-clojure/grammar");
|
|
|
|
const WHITESPACE_CHAR =
|
|
/[\f\n\r\t \u000B\u001C\u001D\u001E\u001F\u2028\u2029\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2008\u2009\u200a\u205f\u3000]/;
|
|
|
|
const WHITESPACE =
|
|
token(repeat1(WHITESPACE_CHAR));
|
|
|
|
const PREC = {
|
|
NUM_LIT: 0,
|
|
NORMAL: 1,
|
|
PACKAGE_LIT: 2,
|
|
DOTTET_LIT: 3,
|
|
KWD_LIT: 4,
|
|
SPECIAL: 5,
|
|
META_LIT: 6,
|
|
}
|
|
|
|
const SYMBOL_HEAD =
|
|
/[^:\f\n\r\t ()\[\]{}"^;`\\,#'\u000B\u001C\u001D\u001E\u001F\u2028\u2029\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2008\u2009\u200a\u205f\u3000]/;
|
|
|
|
const SYMBOL_WITHOUT_SLASH =
|
|
/[^:\f\n\r\t ()\[\]{}"^;/`\\,#'\u000B\u001C\u001D\u001E\u001F\u2028\u2029\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2008\u2009\u200a\u205f\u3000]/;
|
|
|
|
const SYMBOL_BODY =
|
|
choice(SYMBOL_HEAD,
|
|
/[#']/);
|
|
|
|
const SYMBOL =
|
|
token(seq(SYMBOL_HEAD,
|
|
repeat(SYMBOL_BODY)));
|
|
|
|
const STRING =
|
|
token(seq('"',
|
|
repeat(/[^"\\]/),
|
|
repeat(seq("\\",
|
|
/./,
|
|
repeat(/[^"\\]/))),
|
|
'"'));
|
|
|
|
const DIGIT =
|
|
/[0-9]/;
|
|
|
|
const ALPHANUMERIC =
|
|
/[0-9a-zA-Z]/;
|
|
|
|
const HEX_DIGIT =
|
|
/[0-9a-fA-F]/;
|
|
|
|
const OCTAL_DIGIT =
|
|
/[0-7]/;
|
|
|
|
const BINARY_DIGIT =
|
|
/[0-1]/;
|
|
|
|
const HEX_NUMBER =
|
|
seq(choice('#x', '#X'), optional(/[+-]/),
|
|
repeat1(HEX_DIGIT));
|
|
|
|
const OCTAL_NUMBER =
|
|
seq(choice('#o', '#O'), optional(/[+-]/),
|
|
repeat1(OCTAL_DIGIT));
|
|
|
|
const BINARY_NUMBER =
|
|
seq(choice('#b', '#B'), optional(/[+-]/),
|
|
repeat1(BINARY_DIGIT));
|
|
|
|
const RADIX_NUMBER =
|
|
seq('#',
|
|
repeat1(DIGIT),
|
|
/[rR]/,
|
|
repeat1(ALPHANUMERIC));
|
|
|
|
// XXX: not accounting for division by zero
|
|
const RATIO =
|
|
seq(repeat1(DIGIT),
|
|
"/",
|
|
repeat1(DIGIT));
|
|
|
|
const DOUBLE =
|
|
seq(repeat1(DIGIT),
|
|
optional(seq(".",
|
|
repeat(DIGIT))),
|
|
optional(seq(/[eEsSfFdDlL]/,
|
|
optional(/[+-]/),
|
|
repeat1(DIGIT))),
|
|
);
|
|
|
|
const INTEGER =
|
|
seq(repeat1(DIGIT),
|
|
optional(/[MN]/));
|
|
|
|
const NUMBER =
|
|
token(seq(optional(/[+-]/),
|
|
choice(
|
|
HEX_NUMBER,
|
|
OCTAL_NUMBER,
|
|
RADIX_NUMBER,
|
|
BINARY_NUMBER,
|
|
RATIO,
|
|
DOUBLE,
|
|
INTEGER)));
|
|
|
|
|
|
function clSymbol(symbol) {
|
|
return seq(optional(seq('cl', ':')), symbol)
|
|
}
|
|
|
|
function loopSymbol(symbol) {
|
|
return seq(optional(seq(optional('cl'), ':')), symbol)
|
|
}
|
|
|
|
function optSeq(...args) {
|
|
return optional(seq(...args))
|
|
}
|
|
|
|
|
|
module.exports = grammar(clojure, {
|
|
name: 'commonlisp',
|
|
|
|
extras: ($, original) => [...original, $.block_comment],
|
|
conflicts: ($,
|
|
original) => [...original,
|
|
[$.for_clause_word, $.package_lit],
|
|
[$.with_clause, $.package_lit],
|
|
[$.with_clause],
|
|
[$.for_clause],
|
|
[$.accumulation_clause]],
|
|
|
|
|
|
rules: {
|
|
block_comment: _ => token(seq('#|', repeat(choice(/[^|]/, /\|[^#]/)), '|#')),
|
|
|
|
fancy_literal: _ => token(seq('|', repeat(/[^|]/), '|')),
|
|
|
|
_ws: _ =>
|
|
WHITESPACE,
|
|
|
|
unquoting_lit: $ =>
|
|
seq(field('marker', ","),
|
|
repeat($._gap),
|
|
field('value', $._form)),
|
|
|
|
unquote_splicing_lit: $ =>
|
|
seq(repeat($._metadata_lit),
|
|
field('marker', ",@"),
|
|
repeat($._gap),
|
|
field('value', $._form)),
|
|
|
|
syn_quoting_lit: $ =>
|
|
seq(field('marker', "`"),
|
|
repeat($._gap),
|
|
field('value', ($._form))),
|
|
|
|
defun: $ =>
|
|
prec(PREC.SPECIAL, seq(field('open', "("),
|
|
optional($._gap),
|
|
$.defun_header,
|
|
optional($._gap),
|
|
repeat(choice(field('value', $._form), $._gap)),
|
|
field('close', ")"))),
|
|
|
|
_format_token: $ => choice(alias(NUMBER, $.num_lit), seq("'", alias(/./, $.char_lit))),
|
|
// https://en.wikipedia.org/wiki/Format_Common_Lisp)
|
|
format_prefix_parameters: _ => choice('v', 'V', '#'),
|
|
format_modifiers: $ => seq(repeat(choice($._format_token, ',')), choice('@', '@:', ':', ':@')),
|
|
//format_modifiers: _ => choice('@', '@:', ':', ':@'),
|
|
format_directive_type: $ => choice(
|
|
seq(optional(field('repetitions', $._format_token)), choice('~', '%', '&', '|')),
|
|
/[cC]/,
|
|
/\^/,
|
|
'\n',
|
|
'\r',
|
|
/[pP]/,
|
|
/[iI]/,
|
|
/[wW]/,
|
|
/[aA]/,
|
|
'_',
|
|
/[()]/,
|
|
/[{}]/,
|
|
/[\[\]]/,
|
|
/[<>]/,
|
|
';',
|
|
seq(field('numberOfArgs', $._format_token), '*'),
|
|
seq('/', choice(alias($._package_lit_without_slash, $.package_lit), $._sym_lit_without_slash), '/'),
|
|
'?',
|
|
"Newline",
|
|
seq(repeat(choice($._format_token, ',')), /[$rRbBdDgGxXeEoOsStTfF]/),
|
|
),
|
|
format_specifier: $ =>
|
|
prec.left(seq(
|
|
'~',
|
|
optional($.format_prefix_parameters),
|
|
optional($.format_modifiers),
|
|
prec(5, $.format_directive_type),
|
|
)),
|
|
|
|
str_lit: $ =>
|
|
seq(
|
|
'"',
|
|
repeat(choice(
|
|
token.immediate(prec(1, /[^\\~"]+/)),
|
|
token.immediate(seq(/\\./)),
|
|
$.format_specifier,
|
|
)),
|
|
optional('~'),
|
|
'"',
|
|
),
|
|
|
|
for_clause_word: _ => loopSymbol(choice(
|
|
'in',
|
|
'across',
|
|
'being',
|
|
'using',
|
|
/being (the|each) (hash-key[s]?|hash-value[s]?|present-symbol[s]?) (in|of)/,
|
|
'below',
|
|
'above',
|
|
'from',
|
|
'to',
|
|
'upto',
|
|
'upfrom',
|
|
'downto',
|
|
'downfrom',
|
|
'on',
|
|
'by',
|
|
'then',
|
|
'=')),
|
|
|
|
|
|
_for_part: $ => seq(repeat($._gap), $.for_clause_word, repeat($._gap), $._form),
|
|
|
|
accumulation_verb: _ => loopSymbol(/(maximize|minimize|(collect|append|nconc|count)(ing)?|sum(ming)?|maximizing|minimizing)/),
|
|
for_clause: $ => choice(seq(choice(loopSymbol('for'), loopSymbol('and'), loopSymbol('as')), repeat($._gap), field('variable', $._form), optional(field('type', seq(repeat($._gap), $._form))),
|
|
repeat1($._for_part)), loopSymbol('and')),
|
|
with_clause: $ => seq(loopSymbol('with'), repeat($._gap), choice($._form, seq($._form, repeat($._gap), field('type', $._form))), repeat($._gap), optSeq(loopSymbol("="), repeat($._gap)), optSeq($._form, repeat($._gap))),
|
|
do_clause: $ => prec.left(seq(loopSymbol('do'), repeat1(prec.left(seq(repeat($._gap), $._form, repeat($._gap)))))),
|
|
while_clause: $ => prec.left(seq(choice(loopSymbol('while'), loopSymbol('until')), repeat($._gap), $._form)),
|
|
repeat_clause: $ => prec.left(seq(loopSymbol('repeat'), repeat($._gap), $._form)),
|
|
condition_clause: $ => prec.left(choice(seq(choice(loopSymbol('when'), loopSymbol('if'), loopSymbol('unless'), loopSymbol('always'), loopSymbol('thereis'), loopSymbol('never')), repeat($._gap), $._form), loopSymbol("else"))),
|
|
accumulation_clause: $ => seq($.accumulation_verb, repeat($._gap), $._form, optional(seq(repeat($._gap), loopSymbol('into'), repeat($._gap), $._form))),
|
|
termination_clause: $ => prec.left(seq(choice(loopSymbol('finally'), loopSymbol('return'), loopSymbol('initially')), repeat($._gap), $._form)),
|
|
|
|
|
|
loop_clause: $ =>
|
|
seq(choice(
|
|
$.for_clause,
|
|
$.do_clause,
|
|
$.list_lit,
|
|
$.while_clause,
|
|
$.repeat_clause,
|
|
$.accumulation_clause,
|
|
$.condition_clause,
|
|
$.with_clause,
|
|
$.termination_clause,
|
|
$.while_clause,
|
|
)),
|
|
|
|
loop_macro: $ =>
|
|
prec(PREC.SPECIAL,
|
|
seq(field('open', "("),
|
|
optional($._gap),
|
|
clSymbol('loop'),
|
|
repeat(choice($.loop_clause, $._gap)),
|
|
field('close', ")"))),
|
|
|
|
defun_keyword: _ => prec(10, clSymbol(choice('defun', 'defmacro', 'defgeneric', 'defmethod'))),
|
|
|
|
defun_header: $ =>
|
|
prec(PREC.SPECIAL, choice(
|
|
seq(field('keyword', $.defun_keyword),
|
|
repeat($._gap),
|
|
choice($.unquoting_lit, $.unquote_splicing_lit)
|
|
),
|
|
seq(field('keyword', $.defun_keyword),
|
|
repeat($._gap),
|
|
field('function_name', $._form),
|
|
optional(field('specifier', seq(repeat($._gap), choice($.kwd_lit, $.sym_lit)))),
|
|
repeat($._gap),
|
|
field('lambda_list', choice($.list_lit, $.unquoting_lit))),
|
|
seq(field('keyword', alias('lambda', $.defun_keyword)),
|
|
repeat($._gap),
|
|
field('lambda_list', choice($.list_lit, $.unquoting_lit)))
|
|
)),
|
|
|
|
array_dimension: _ => prec(100, /\d+[aA]/),
|
|
|
|
char_lit: _ =>
|
|
seq('#', /\\([^\f\n\r\t ()]+|[()])/),
|
|
|
|
vec_lit: $ =>
|
|
prec(PREC.SPECIAL,
|
|
choice(
|
|
seq(field('open', choice('#0A', '#0a')), choice($.num_lit, $.complex_num_lit)),
|
|
seq(field('open', '#'), optional($.array_dimension), $.list_lit))),
|
|
|
|
path_lit: $ =>
|
|
prec(PREC.SPECIAL,
|
|
seq(field('open', choice('#P', '#p')), alias(STRING, $.str_lit))),
|
|
|
|
_bare_list_lit: $ =>
|
|
choice(prec(PREC.SPECIAL, $.defun),
|
|
prec(PREC.SPECIAL, $.loop_macro),
|
|
seq(field('open', "("),
|
|
repeat(choice(field('value', $._form), $._gap)),
|
|
field('close', ")"))),
|
|
|
|
package_lit: $ => prec(PREC.PACKAGE_LIT, choice(seq(
|
|
field('package', choice($.sym_lit, 'cl')), // Make optional, instead of keywords?
|
|
choice(':', '::'),
|
|
field('symbol', $.sym_lit)
|
|
), prec(1, 'cl'))),
|
|
|
|
_package_lit_without_slash: $ => seq(
|
|
field('package', choice($._sym_lit_without_slash, 'cl')), // Make optional, instead of keywords?
|
|
choice(':', '::'),
|
|
field('symbol', $._sym_lit_without_slash)
|
|
),
|
|
|
|
kwd_lit: $ => prec(PREC.KWD_LIT, seq(
|
|
choice(':', '::'),
|
|
$.kwd_symbol,
|
|
)),
|
|
|
|
sym_lit: _ =>
|
|
seq(SYMBOL),
|
|
|
|
_sym_lit_without_slash: $ =>
|
|
alias(repeat1(SYMBOL_WITHOUT_SLASH), $.sym_lit),
|
|
|
|
kwd_symbol: _ =>
|
|
seq(SYMBOL),
|
|
|
|
self_referential_reader_macro: _ => /#\d+[=#]/,
|
|
|
|
_form: $ =>
|
|
seq(optional('#'),
|
|
choice(
|
|
$.num_lit,
|
|
$.fancy_literal,
|
|
$.vec_lit,
|
|
$.kwd_lit,
|
|
// No idea why this is necessary...It is never used but triggers some background magic
|
|
alias(seq(field('open', '#'), optional(/\d+[aA]/), $.list_lit), $.vec_lit),
|
|
$.str_lit,
|
|
$.self_referential_reader_macro,
|
|
$.char_lit,
|
|
$.nil_lit,
|
|
$.path_lit,
|
|
$.sym_lit,
|
|
$.package_lit,
|
|
$.list_lit,
|
|
$.set_lit,
|
|
$.read_cond_lit,
|
|
$.splicing_read_cond_lit,
|
|
$.var_quoting_lit,
|
|
$.quoting_lit,
|
|
$.syn_quoting_lit,
|
|
$.unquote_splicing_lit,
|
|
$.unquoting_lit,
|
|
$.include_reader_macro,
|
|
$.complex_num_lit,
|
|
".",
|
|
)),
|
|
|
|
num_lit: _ =>
|
|
seq(NUMBER, optional(/[sSfFdDlL]/)),
|
|
|
|
include_reader_macro: $ =>
|
|
seq(repeat($._metadata_lit),
|
|
field('marker', choice("#+", "#-")),
|
|
repeat($._gap),
|
|
field('condition', $._form),
|
|
repeat($._gap),
|
|
field('target', $._form)),
|
|
|
|
complex_num_lit: $ =>
|
|
seq(repeat($._metadata_lit),
|
|
field('marker', choice("#C", "#c")),
|
|
repeat($._gap),
|
|
'(',
|
|
repeat($._gap),
|
|
field('real', $.num_lit), // only numbers allowed here
|
|
repeat($._gap),
|
|
field('imaginary', $.num_lit),
|
|
repeat($._gap),
|
|
')'
|
|
),
|
|
}
|
|
});
|