/* * grammar.js * Copyright (C) 2021 Stephan Seitz * 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], [$.loop_macro, $.defun_keyword, $.package_lit]], 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), ')' ), } });