const PREC = {
COMMENT: { ASSOC: prec, RANK: -1},
BLOCK: { ASSOC: prec.right, RANK: 0 },
DOT_DOT_I: { ASSOC: prec, RANK: 1 },
HELP: { ASSOC: prec.left, RANK: 1 },
FUNCTION_OR_LOOP: { ASSOC: prec.right, RANK: 2 },
IF: { ASSOC: prec.right, RANK: 3 },
LEFT_ASSIGN: { ASSOC: prec.right, RANK: 4 },
EQUALS_ASSIGN: { ASSOC: prec.right, RANK: 5 },
RIGHT_ASSIGN: { ASSOC: prec.left, RANK: 6 },
TILDE: { ASSOC: prec.left, RANK: 7 },
OR: { ASSOC: prec.left, RANK: 8 },
AND: { ASSOC: prec.left, RANK: 9 },
UNARY_NOT: { ASSOC: prec.left, RANK: 10 },
COMPARISON: { ASSOC: prec.left, RANK: 11 },
PLUS_MINUS: { ASSOC: prec.left, RANK: 12 },
MULTIPLY_DIVIDE: { ASSOC: prec.left, RANK: 13 },
SPECIAL_OR_PIPE: { ASSOC: prec.left, RANK: 14 },
COLON: { ASSOC: prec.left, RANK: 15 },
UNARY_PLUS_MINUS: { ASSOC: prec.left, RANK: 16 },
EXPONENTIATE: { ASSOC: prec.right, RANK: 17 },
EXTRACT: { ASSOC: prec.right, RANK: 18 },
NAMESPACE: { ASSOC: prec.right, RANK: 19 },
CALL: { ASSOC: prec.right, RANK: 20 },
}
module.exports = grammar({
name: 'r',
extras: $ => [
$.comment,
/\s/
],
externals: $ => [
$._newline,
$._semicolon,
$._raw_string_literal,
$._external_else,
$._external_open_parenthesis,
$._external_close_parenthesis,
$._external_open_brace,
$._external_close_brace,
$._external_open_bracket,
$._external_close_bracket,
$._external_open_bracket2,
$._external_close_bracket2
],
word: $ => $.identifier,
rules: {
program: $ => repeat(choice($._expression, $._semicolon, $._newline)),
function_definition: $ => withPrec(PREC.FUNCTION_OR_LOOP, seq(
field("name", choice("\\", "function")),
field("parameters", $.parameters),
repeat($._newline),
optional(field("body", $._expression))
)),
parameters: $ => seq(
field("open", $._open_parenthesis),
optional(seq(
field("parameter", $.parameter),
repeat(seq($.comma, field("parameter", $.parameter)))
)),
field("close", $._close_parenthesis)
),
parameter: $ => choice(
$._parameter_with_default,
$._parameter_without_default
),
_parameter_with_default: $ => seq(
field("name", $.identifier),
"=",
optional(field("default", $._expression))
),
_parameter_without_default: $ => field("name", choice($.identifier, $.dots)),
if_statement: $ => withPrec(PREC.IF, seq(
"if",
repeat($._newline),
field("open", $._open_parenthesis),
field("condition", $._expression),
field("close", $._close_parenthesis),
repeat($._newline),
field("consequence", $._expression),
optional(seq(
$._else,
field("alternative", $._expression)
))
)),
for_statement: $ => withPrec(PREC.FUNCTION_OR_LOOP, seq(
"for",
repeat($._newline),
field("open", $._open_parenthesis),
field("variable", $.identifier),
"in",
field("sequence", $._expression),
field("close", $._close_parenthesis),
repeat($._newline),
optional(field("body", $._expression))
)),
while_statement: $ => withPrec(PREC.FUNCTION_OR_LOOP, seq(
"while",
repeat($._newline),
field("open", $._open_parenthesis),
field("condition", $._expression),
field("close", $._close_parenthesis),
repeat($._newline),
optional(field("body", $._expression))
)),
repeat_statement: $ => withPrec(PREC.FUNCTION_OR_LOOP, seq(
"repeat",
repeat($._newline),
optional(field("body", $._expression))
)),
braced_expression: $ => withPrec(PREC.BLOCK, seq(
field("open", $._open_brace),
repeat(field("body", choice($._expression, $._semicolon, $._newline))),
optional(field("close", $._close_brace))
)),
parenthesized_expression: $ => withPrec(PREC.BLOCK, seq(
field("open", $._open_parenthesis),
repeat(field("body", choice($._expression, $._newline))),
optional(field("close", $._close_parenthesis))
)),
call: $ => withPrec(PREC.CALL, seq(
field("function", $._expression),
field("arguments", alias($.call_arguments, $.arguments))
)),
subset: $ => withPrec(PREC.CALL, seq(
field("function", $._expression),
field("arguments", alias($.subset_arguments, $.arguments))
)),
subset2: $ => withPrec(PREC.CALL, seq(
field("function", $._expression),
field("arguments", alias($.subset2_arguments, $.arguments))
)),
call_arguments: $ => seq(
field("open", $._open_parenthesis),
repeat($._argument),
field("close", $._close_parenthesis)
),
subset_arguments: $ => seq(
field("open", $._open_bracket),
repeat($._argument),
field("close", $._close_bracket)
),
subset2_arguments: $ => seq(
field("open", $._open_bracket2),
repeat($._argument),
field("close", $._close_bracket2)
),
_argument: $ => choice(
$.comma,
field("argument", $.argument)
),
argument: $ => choice(
$._argument_named,
$._argument_unnamed
),
_argument_named: $ => prec.right(1, seq(
field("name", choice($.dots, $.identifier, $.string)),
"=",
optional($._argument_value)
)),
_argument_unnamed: $ => $._argument_value,
_argument_value: $ => field("value", choice($._expression, $._newline)),
unary_operator: $ => {
const table = [
["?", PREC.HELP],
["~", PREC.TILDE],
["!", PREC.UNARY_NOT],
["+", PREC.UNARY_PLUS_MINUS],
["-", PREC.UNARY_PLUS_MINUS]
];
return choice(...table.map(([operator, prec]) => prec.ASSOC(prec.RANK, seq(
field("operator", operator),
repeat($._newline),
field("rhs", $._expression)
))))
},
binary_operator: $ => {
const table = [
["?", PREC.HELP],
["~", PREC.TILDE],
["<-", PREC.LEFT_ASSIGN],
["<<-", PREC.LEFT_ASSIGN],
[":=", PREC.LEFT_ASSIGN],
["->", PREC.RIGHT_ASSIGN],
["->>", PREC.RIGHT_ASSIGN],
["=", PREC.EQUALS_ASSIGN],
["|", PREC.OR],
["&", PREC.AND],
["||", PREC.OR],
["&&", PREC.AND],
["<", PREC.COMPARISON],
["<=", PREC.COMPARISON],
[">", PREC.COMPARISON],
[">=", PREC.COMPARISON],
["==", PREC.COMPARISON],
["!=", PREC.COMPARISON],
["+", PREC.PLUS_MINUS],
["-", PREC.PLUS_MINUS],
["*", PREC.MULTIPLY_DIVIDE],
["/", PREC.MULTIPLY_DIVIDE],
["**", PREC.EXPONENTIATE],
["^", PREC.EXPONENTIATE],
[alias(/%[^%\\\n]*%/, "special"), PREC.SPECIAL_OR_PIPE],
["|>", PREC.SPECIAL_OR_PIPE],
[":", PREC.COLON]
];
return choice(...table.map(([operator, prec]) => prec.ASSOC(prec.RANK, seq(
field("lhs", $._expression),
field("operator", operator),
repeat($._newline),
field("rhs", $._expression)
))))
},
extract_operator: $ => {
const table = [
["$", PREC.EXTRACT],
["@", PREC.EXTRACT]
];
return choice(...table.map(([operator, prec]) => prec.ASSOC(prec.RANK, seq(
field("lhs", $._expression),
field("operator", operator),
repeat($._newline),
optional(field("rhs", $._string_or_identifier))
))))
},
namespace_operator: $ => {
const table = [
["::", PREC.NAMESPACE],
[":::", PREC.NAMESPACE]
];
return choice(...table.map(([operator, prec]) => prec.ASSOC(prec.RANK, seq(
field("lhs", $._string_or_identifier),
field("operator", operator),
optional(field("rhs", $._string_or_identifier))
))))
},
integer: $ => seq($._float_literal, "L"),
complex: $ => seq($._float_literal, "i"),
float: $ => $._float_literal,
_hex_literal: $ => seq(/0[xX][0-9a-fA-F]+/),
_number_literal: $ => /(?:(?:\d+(?:\.\d*)?)|(?:\.\d+))(?:[eE][+-]?\d*)?/,
_float_literal: $ => choice($._hex_literal, $._number_literal),
string: $ => choice(
$._raw_string_literal,
$._single_quoted_string,
$._double_quoted_string
),
_single_quoted_string: $ => seq(
'\'',
optional(field("content", alias($._single_quoted_string_content, $.string_content))),
'\''
),
_double_quoted_string: $ => seq(
'"',
optional(field("content", alias($._double_quoted_string_content, $.string_content))),
'"'
),
_single_quoted_string_content: $ => repeat1(choice(
/[^'\\]+/,
$.escape_sequence
)),
_double_quoted_string_content: $ => repeat1(choice(
/[^"\\]+/,
$.escape_sequence
)),
escape_sequence: $ => token.immediate(seq(
'\\',
choice(
/[^0-9xuU]/,
/[0-7]{1,3}/,
/x[0-9a-fA-F]{1,2}/,
/u[0-9a-fA-F]{1,4}/,
/u\{[0-9a-fA-F]{1,4}\}/,
/U[0-9a-fA-F]{1,8}/,
/U\{[0-9a-fA-F]{1,8}\}/
)
)),
identifier: $ => {
const _identifier = /[\p{XID_Start}._][\p{XID_Continue}.]*/;
const _quoted_identifier = /`((?:\\(.|\n))|[^`\\])*`/;
return token(
choice(
_identifier,
_quoted_identifier
)
)
},
return: $ => "return",
next: $ => "next",
break: $ => "break",
true: $ => "TRUE",
false: $ => "FALSE",
null: $ => "NULL",
inf: $ => "Inf",
nan: $ => "NaN",
na: $ => choice(
"NA",
"NA_integer_",
"NA_real_",
"NA_complex_",
"NA_character_"
),
dots: $ => "...",
dot_dot_i: $ => token(withPrec(PREC.DOT_DOT_I, /[.][.]\d+/)),
_expression: $ => choice(
$.function_definition,
$.if_statement,
$.for_statement,
$.while_statement,
$.repeat_statement,
$.braced_expression,
$.parenthesized_expression,
$.call,
$.subset,
$.subset2,
$.unary_operator,
$.binary_operator,
$.extract_operator,
$.namespace_operator,
$.integer,
$.complex,
$.float,
$.string,
$.identifier,
$.return,
$.next,
$.break,
$.true,
$.false,
$.null,
$.inf,
$.nan,
$.na,
$.dots,
$.dot_dot_i
),
comment: $ => token(withPrec(PREC.COMMENT, /#.*/)),
comma: $ => ",",
_string_or_identifier: $ => choice($.string, $.identifier),
_else: $ => alias($._external_else, "else"),
_open_parenthesis: $ => alias($._external_open_parenthesis, "("),
_close_parenthesis: $ => alias($._external_close_parenthesis, ")"),
_open_brace: $ => alias($._external_open_brace, "{"),
_close_brace: $ => alias($._external_close_brace, "}"),
_open_bracket: $ => alias($._external_open_bracket, "["),
_close_bracket: $ => alias($._external_close_bracket, "]"),
_open_bracket2: $ => alias($._external_open_bracket2, "[["),
_close_bracket2: $ => alias($._external_close_bracket2, "]]")
}
})
function withPrec(prec, rule) {
return prec.ASSOC(prec.RANK, rule)
}