|
|
const {parens, brackets, braces, sep1, layouted, qualified} = require('./util.js')
|
|
|
|
|
|
module.exports = {
|
|
|
// ------------------------------------------------------------------------
|
|
|
// expression
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
exp_parens: $ => parens($._exp),
|
|
|
|
|
|
/**
|
|
|
* This needs to be disambiguated from `gcon_tuple`, which is a constructor with _only_ commas.
|
|
|
* Tuple sections aren't allowed in patterns.
|
|
|
*
|
|
|
* Since tuple expressions can contain singular expressions in sections like `(a,)` and `(,a)`, it has to be ensured
|
|
|
* that there is _at least_ each one comma and one expression in there, but the comma may be on either side and be
|
|
|
* preceded by any number of further commas, like `(,,,a)`.
|
|
|
*
|
|
|
* The final `repeat` is simpler, it just has to ensure that no two `_exp`s can be successive, but this encoding
|
|
|
* means that the optional `_exp` after `(5,)` needs to be included in the `choice`, otherwise a simple pair would be
|
|
|
* impossible.
|
|
|
*/
|
|
|
_exp_tuple: $ => seq(
|
|
|
choice(seq(repeat1($.comma), $._exp), seq($._exp, $.comma, optional($._exp))),
|
|
|
repeat(seq($.comma, optional($._exp)))
|
|
|
),
|
|
|
|
|
|
exp_tuple: $ => parens($._exp_tuple),
|
|
|
|
|
|
/**
|
|
|
* Unlike their boxed variants, unboxed tuples may be nullary and unary, making it simpler to parse them.
|
|
|
* The nullary tuple may even have no space between the hashes, but this format coincides with the prefix notation of
|
|
|
* the `##` symop. Since the latter is already parsed by other rules and is valid in the same positions, it is left out
|
|
|
* here.
|
|
|
*
|
|
|
* The opening lexeme, `(#`, is parsed with a hardcoded trailing space in exp, pat and type. This is a hack that works
|
|
|
* around some peculiarities of the interactions with some features like TH and symbolic operators that would most
|
|
|
* likely be significantly more complex to implement correctly. As it stands, the grammar can't parse an unboxed sum
|
|
|
* exp without a leading space, as in `(#| x #)`.
|
|
|
*/
|
|
|
exp_unboxed_tuple: $ => seq($._unboxed_open, sep($.comma, optional($._exp)), $._unboxed_close),
|
|
|
|
|
|
/**
|
|
|
* Unboxed sums must have at least one separating `|`, otherwise the expression would be a unary or nullary tuple.
|
|
|
*/
|
|
|
_exp_unboxed_sum: $ => sep2('|', optional($._exp)),
|
|
|
|
|
|
exp_unboxed_sum: $ => seq($._unboxed_open, $._exp_unboxed_sum, $._unboxed_close),
|
|
|
|
|
|
exp_list: $ => brackets(sep1($.comma, $._exp)),
|
|
|
|
|
|
bind_pattern: $ => seq(
|
|
|
$._typed_pat,
|
|
|
'<-',
|
|
|
$._exp,
|
|
|
),
|
|
|
|
|
|
exp_arithmetic_sequence: $ => brackets(
|
|
|
field('from', $._exp),
|
|
|
optional(seq($.comma, field('step', $._exp))),
|
|
|
'..',
|
|
|
optional(field('to', $._exp)),
|
|
|
),
|
|
|
|
|
|
/**
|
|
|
* TransformListComp.
|
|
|
*
|
|
|
* These have to be spelled out because the keywords are valid varids when the extension is disabled and it causes
|
|
|
* errors if they are used individually.
|
|
|
*/
|
|
|
transform: $ => choice(
|
|
|
seq('then group by', $._exp, 'using', $._exp),
|
|
|
seq('then group using', $._exp),
|
|
|
seq('then', $._exp),
|
|
|
),
|
|
|
|
|
|
qual: $ => choice(
|
|
|
$.bind_pattern,
|
|
|
$.let,
|
|
|
$.transform,
|
|
|
$._exp,
|
|
|
),
|
|
|
|
|
|
exp_list_comprehension: $ => brackets(
|
|
|
$._exp,
|
|
|
'|',
|
|
|
sep1($.comma, $.qual),
|
|
|
),
|
|
|
|
|
|
exp_section_left: $ => parens(
|
|
|
$._exp_infix,
|
|
|
$._qop,
|
|
|
),
|
|
|
|
|
|
exp_section_right: $ => parens(
|
|
|
$._qop_nominus,
|
|
|
$._exp_infix,
|
|
|
),
|
|
|
|
|
|
exp_th_quoted_name: $ => choice(
|
|
|
seq(quote, choice($._qvar, $._qcon)),
|
|
|
seq(quote + quote, $._atype),
|
|
|
),
|
|
|
|
|
|
exp_field: $ => choice(
|
|
|
alias('..', $.wildcard),
|
|
|
seq($._qvar, optional(seq('=', $._exp)))
|
|
|
),
|
|
|
|
|
|
exp_type_application: $ => seq('@', $._atype),
|
|
|
|
|
|
exp_lambda: $ => seq(
|
|
|
'\\',
|
|
|
repeat1($._apat),
|
|
|
'->',
|
|
|
$._exp,
|
|
|
),
|
|
|
|
|
|
exp_in: $ => seq('in', $._exp),
|
|
|
|
|
|
let: $ => seq('let', optional($.decls)),
|
|
|
|
|
|
_let_decls: $ => layouted_without_end($, $._decl),
|
|
|
|
|
|
exp_let: $ => seq('let', optional(alias($._let_decls, $.decls))),
|
|
|
|
|
|
exp_let_in: $ => seq($.exp_let, $.exp_in),
|
|
|
|
|
|
exp_cond: $ => seq(
|
|
|
'if',
|
|
|
field('if', $._exp),
|
|
|
optional(';'),
|
|
|
'then',
|
|
|
field('then', $._exp),
|
|
|
optional(';'),
|
|
|
'else',
|
|
|
field('else', $._exp),
|
|
|
),
|
|
|
|
|
|
exp_if_guard: $ => seq('if', prec.left(repeat1($.gdpat))),
|
|
|
|
|
|
pattern_guard: $ => seq(
|
|
|
$._pat,
|
|
|
'<-',
|
|
|
$._exp_infix,
|
|
|
),
|
|
|
|
|
|
guard: $ => choice(
|
|
|
$.pattern_guard,
|
|
|
$.let,
|
|
|
$._exp_infix,
|
|
|
),
|
|
|
|
|
|
guards: $ => seq('|', sep1($.comma, $.guard)),
|
|
|
|
|
|
gdpat: $ => seq($.guards, '->', $._exp),
|
|
|
|
|
|
_alt_variants: $ => choice(
|
|
|
seq('->', $._exp),
|
|
|
repeat1($.gdpat),
|
|
|
),
|
|
|
|
|
|
alt: $ => seq($._pat, $._alt_variants, optional(seq($.where, optional($.decls)))),
|
|
|
|
|
|
alts: $ => layouted($, $.alt),
|
|
|
|
|
|
exp_case: $ => seq('case', $._exp, 'of', $.alts),
|
|
|
|
|
|
/**
|
|
|
* left associative because the alts are optional
|
|
|
*/
|
|
|
exp_lambda_case: $ => seq(
|
|
|
'\\',
|
|
|
'case',
|
|
|
optional($.alts),
|
|
|
),
|
|
|
|
|
|
rec: $ => seq(
|
|
|
'rec',
|
|
|
layouted($, $.stmt),
|
|
|
),
|
|
|
|
|
|
stmt: $ => choice(
|
|
|
$._exp,
|
|
|
$.bind_pattern,
|
|
|
$.let,
|
|
|
$.rec,
|
|
|
),
|
|
|
|
|
|
/**
|
|
|
* TODO does this hide the keyword entirely?
|
|
|
*/
|
|
|
_do_keyword: _ => choice('mdo', 'do'),
|
|
|
|
|
|
do_module: $ => qualified($, $._do_keyword),
|
|
|
|
|
|
exp_do: $ => seq(choice($.do_module, $._do_keyword), layouted($, $.stmt)),
|
|
|
|
|
|
exp_negation: $ => seq('-', $._aexp),
|
|
|
|
|
|
exp_record: $ => seq($._aexp, braces(sep1($.comma, $.exp_field))),
|
|
|
|
|
|
exp_name: $ => choice(
|
|
|
$._qvar,
|
|
|
$._qcon,
|
|
|
$.implicit_parid,
|
|
|
$.label,
|
|
|
),
|
|
|
|
|
|
_aexp: $ => choice(
|
|
|
$.exp_name,
|
|
|
$.exp_parens,
|
|
|
$.exp_tuple,
|
|
|
$.exp_list,
|
|
|
$.exp_th_quoted_name,
|
|
|
$.exp_type_application,
|
|
|
$.exp_lambda_case,
|
|
|
$.exp_do,
|
|
|
$.exp_record,
|
|
|
$.exp_arithmetic_sequence,
|
|
|
$.exp_list_comprehension,
|
|
|
$.exp_section_left,
|
|
|
$.exp_section_right,
|
|
|
$.exp_unboxed_tuple,
|
|
|
$.exp_unboxed_sum,
|
|
|
$.splice,
|
|
|
$.quasiquote,
|
|
|
alias($.literal, $.exp_literal),
|
|
|
),
|
|
|
|
|
|
/**
|
|
|
* Function application.
|
|
|
*
|
|
|
* This convoluted rule is necessary because of BlockArguments with lambda – if `exp_lambda` is in `lexp` as is stated
|
|
|
* in the reference, it can only occur after an infix operator; if it is in `aexp`, it causes lots of problems.
|
|
|
* Furthermore, the strange way the recursion is done here is to avoid local conflicts.
|
|
|
*/
|
|
|
_exp_apply: $ => choice(
|
|
|
$._aexp,
|
|
|
seq($._aexp, $._exp_apply),
|
|
|
seq($._aexp, $.exp_lambda),
|
|
|
seq($._aexp, $.exp_let_in),
|
|
|
seq($._aexp, $.exp_cond),
|
|
|
seq($._aexp, $.exp_case),
|
|
|
),
|
|
|
|
|
|
/**
|
|
|
* The point of this `choice` is to get a node for function application only if there is more than one expression
|
|
|
* present.
|
|
|
*/
|
|
|
_fexp: $ => choice(
|
|
|
$._aexp,
|
|
|
alias($._exp_apply, $.exp_apply),
|
|
|
),
|
|
|
|
|
|
_lexp: $ => choice(
|
|
|
$.exp_let_in,
|
|
|
$.exp_cond,
|
|
|
$.exp_if_guard,
|
|
|
$.exp_case,
|
|
|
$.exp_negation,
|
|
|
$._fexp,
|
|
|
$.exp_lambda,
|
|
|
),
|
|
|
|
|
|
/**
|
|
|
* This is left-associative, although in reality this would depend on the fixity declaration for the operator.
|
|
|
* The default is left, even though the reerence specifies it the other way around.
|
|
|
* In any case, this seems to be more stable.
|
|
|
*/
|
|
|
exp_infix: $ => seq($._exp_infix, $._qop, $._lexp),
|
|
|
|
|
|
_exp_infix: $ => choice(
|
|
|
$.exp_infix,
|
|
|
$._lexp,
|
|
|
),
|
|
|
|
|
|
/**
|
|
|
* `prec.right` because:
|
|
|
*
|
|
|
* let x = 1 in x :: Int
|
|
|
*
|
|
|
* here the type annotation binds to `x`, not the entire expression
|
|
|
*/
|
|
|
_exp: $ => prec.right(seq($._exp_infix, optional($._type_annotation))),
|
|
|
}
|