tree-sitter-org/grammar.js
2021-04-11 01:07:45 -04:00

434 lines
9.7 KiB
JavaScript

org_grammar = {
// EXTERNALS, INLINE =================================== {{{1
name: 'org',
extras: _ => [' '], // Treat newlines explicitly
externals: $ => [
$._liststart,
$._listend,
$._listitemend,
$._bullet,
$.stars,
$._sectionend,
$._markup,
],
// inline: $ => [$._word, $._numbers, $._junk],
// inline: $ => [ $._activeStart, $._activeEnd, $._inactiveStart, $._inactiveEnd,
// $._tsSeparator, $._ymd, $._dayname,],
// PRECEDENCES, CONFLICT =============================== {{{1
precedences: _ => [
['section', 'element', 'paragraph', 'textelement'],
['plan', 'textelement'],
['fn_definition', 'footnote'],
],
conflicts: $ => [
[$._text, $.bold],
[$._text, $.italic],
[$._text, $.underline],
[$._text, $.strikethrough],
[$._text, $.code],
[$._text, $.verbatim],
[$.item],
[$._lastitem],
],
rules: {
// DOCUMENT, SECTIONS, BODY, & PARAGRAPH =============== {{{1
document: $ => seq(
optional($.body),
repeat($.section),
),
// SECTIONS, BODY, PARAGRAPH =========================== {{{1
section: $ => prec.dynamic(1, prec('section',
seq(
$.headline, $._eol,
optional(seq(
optional(seq($.plan, $._eol)),
optional(seq($.property_drawer, $._eol)),
optional($.body),
repeat($.section),
)),
$._sectionend,
))),
_eol: _ => choice('\0', '\n', '\r'),
_nl: _ => choice('\n', '\r'),
body: $ => choice(
repeat1($._eol),
seq(
repeat($._eol),
repeat1(seq(
choice(
$._element,
$.paragraph
),
repeat($._eol),
)),
)),
paragraph: $ => prec.right('paragraph',
repeat1(seq(
repeat1($._textelement),
$._eol)
)),
// ELEMENT AND TEXTELEMENT ============================= {{{1
_element: $ => choice(
$.drawer,
$.comment,
$.fndef,
$.directive,
$.list,
$.block,
$.dynamic_block,
// $.table,
),
_textelement: $ => prec('textelement',
choice(
$._text,
$.timestamp,
$.footnote,
$.link,
$.bold,
$.code,
$.italic,
$.verbatim,
$.underline,
$.strikethrough,
// $.subscript
// $.superscript
// $.latexfragment
)),
// HEADLINES =========================================== {{{1
headline: $ => seq(
$.stars,
$.item,
optional($._taglist),
),
item: $ => repeat1(choice($._text, ':')),
_taglist: $ => prec.dynamic(1, // otherwise just item
seq(':',
repeat1(seq(
$.tag,
token.immediate(':')
)))),
tag: _ => token.immediate(/[\p{L}\p{N}_@#%]+/),
_propertyName: _ => /:\p{Z}*:/,
property_drawer: $ => seq(
':PROPERTIES:', $._eol,
repeat(prec.right(seq(optional($.property), repeat1($._eol)))),
':END:',
),
property: $ => seq(
$._propertyName,
repeat($._text),
),
// PLANNING ============================================ {{{1
_scheduled: _ => 'SCHEDULED:',
_deadline: _ => 'DEADLINE:',
_closed: _ => 'CLOSED:',
plan: $ => repeat1(prec('plan',
choice(
$.timestamp,
$.scheduled,
$.deadline,
$.closed,
))),
scheduled: $ => seq($._scheduled, $.timestamp),
deadline: $ => seq($._deadline, $.timestamp),
closed: $ => seq(
$._closed,
alias(choice(
$._inactiveTimestamp,
$._inactiveTimestampRange,
), $.timestamp),
),
// TIMESTAMP =========================================== {{{1
_activeStart: _ => '<',
_activeEnd: _ => '>',
_inactiveStart: _ => '[',
_inactiveEnd: _ => ']',
_tsSeparator: _ => '--',
_ymd: _ => /\p{N}{1,4}-\p{N}{1,2}-\p{N}{1,4}/,
time: _ => /\p{N}?\p{N}:\p{N}\p{N}/,
repeater: _ => /[.+]?\+\p{N}+\p{L}/,
delay: _ => /--?\p{N}+\p{L}/,
date: $ => seq($._ymd, optional(/\p{L}+/)),
timestamp: $ => choice(
$._activeTimestamp,
$._activeTimestampRange,
$._inactiveTimestamp,
$._inactiveTimestampRange,
),
_activeTimestamp: $ => seq(
$._activeStart,
$.date,
optional($.time),
optional($.repeater),
optional($.delay),
$._activeEnd,
),
_inactiveTimestamp: $ => seq(
$._inactiveStart,
$.date,
optional($.time),
optional($.repeater),
optional($.delay),
$._inactiveEnd,
),
_activeTimestampRange: $ => choice(
seq(
alias($._activeTimestamp, $.timestamp),
$._tsSeparator,
alias($._activeTimestamp, $.timestamp)),
seq(
$._activeStart,
$.date,
$.time, '-', $.time,
optional($.repeater),
optional($.delay),
$._activeEnd,
)
),
_inactiveTimestampRange: $ => choice(
seq($._inactiveTimestamp, $._tsSeparator, $._inactiveTimestamp),
seq(
$._inactiveStart,
$.date,
$.time, '-', $.time,
optional($.repeater),
optional($.delay),
$._inactiveEnd,
)
),
// MARKUP ============================================== {{{1
bold: make_markup('*'),
italic: make_markup('/'),
underline: make_markup('_'),
strikethrough: make_markup('+'),
code: make_markup('~', true),
verbatim: make_markup('=', true),
// LINK ================================================ {{{1
_linkstart: _ => '[[',
_linksep: _ => '][',
_linkend: _ => ']]',
link: $ => seq(
$._linkstart,
optional(seq(field('uri', $.linktext), $._linksep)),
field('description', $.linktext),
$._linkend,
),
linktext: _ => /[^\]]*/,
// FOOTNOTE ============================================ {{{1
_fn_label: _ => /[^\p{Z}\[\]]+/,
_fn: _ => '[fn:',
fndef: $ => prec('fn_definition',
seq(
$._fn,
$._fn_label,
']',
$.paragraph,
)),
footnote: $ => prec('footnote',
seq(
$._fn,
choice(
$._fn_label,
seq(optional($._fn_label), ':', repeat1($._fn_label)),
),
']',
)),
// DIRECTIVE =========================================== {{{1
directive: $ => seq(
'#+',
token.immediate(/[^\p{Z}:]+/), // name
token.immediate(':'),
repeat($._text),
$._eol,
),
// COMMENTS ============================================ {{{1
comment: $ => prec.right(repeat1(seq(
'# ', repeat($._text), $._eol
))),
// DRAWER ============================================== {{{1
drawer: $ => seq(
':',
token.immediate(/[\p{L}\p{N}\p{Pd}\p{Pc}]+/),
token.immediate(':'),
$._eol,
optional($.body),
':END:',
$._eol,
),
// BLOCK =============================================== {{{1
block: $ => seq(
'#+BEGIN_',
alias($._name, $.name),
optional($.parameters),
$._nl,
alias(
repeat(seq(
repeat($._textonly),
$._nl,
)),
$.contents),
'#+END_', $._name, // \P{Z} does not match newlines
repeat($._junk), // FIXME
$._eol,
),
_name: _ => token.immediate(/[^\p{Z}\n\r]+/),
// DYNAMIC BLOCK ======================================= {{{1
dynamic_block: $ => prec(1, seq( // FIXME why is this precedence required?
'#+BEGIN:',
optional(alias($._text, $.name)),
optional($.parameters),
// optional(alias(repeat1(/\S+/), $.parameters)),
$._eol,
alias(repeat(seq(
repeat($._textonly),
$._nl,
)), $.contents),
'#+END:',
repeat($._junk), // FIXME
$._eol,
)),
parameters: $ => repeat1($._text),
// LISTS =============================================== {{{1
list: $ => seq(
$._liststart,
repeat(seq($.listitem, optional($._eol))),
alias($._lastitem, $.listitem),
),
listitem: $ => seq(
$._bullet,
optional($._checkbox),
optional($._itemtag),
optional($._itemtext),
$._listitemend,
$._eol,
),
_lastitem: $ => seq(
$._bullet,
optional($._checkbox),
optional($._itemtag),
optional($._itemtext),
$._listend,
optional($._eol),
),
_checkbox: _ => /\[[ xX-]\]/,
_itemtag: $ => seq(repeat($._textelement), '::'),
_itemtext: $ => seq(
repeat1($._textelement),
repeat(seq(
$._eol,
optional($._eol),
choice(repeat1($._textelement), $.list)
)),
),
// TEXT ================================================ {{{1
// TODO: inline word/numbers/junk. Causes precedence issues
// A repeat would also be nice.
_textonly: $ => choice($._word,
$._numbers,
$._junk,
),
_text: $ => choice(
$._word,
$._numbers,
$._junk,
$._activeStart, // Causes conflicts, so they get marked as text.
$._inactiveStart,
seq($._markup, '*'),
seq($._markup, '/'),
seq($._markup, '_'),
seq($._markup, '+'),
seq($._markup, '~'),
seq($._markup, '='),
'#', // comment collision
),
_word: _ => /\p{L}+/,
_numbers: _ => /\p{N}+/,
_junk: _ => /[^\p{Z}\p{L}\p{N}]/,
}
};
function make_markup(delim, textonly = false) { // {{{1
return $ => prec.left(seq(
$._markup,
delim,
repeat1(textonly ? $._text : $._textelement),
repeat(seq($._eol, repeat1(textonly ? $._text : $._textelement))),
token.immediate(delim),
))
}
// }}}
module.exports = grammar(org_grammar);