tree-sitter-diff 0.1.0

diff grammar for the tree-sitter parsing library
Documentation
const NEWLINE = /\r?\n/;
const WHITE_SPACE = /[\t\f\v ]+/;
const ANYTHING = /[^\r\n]+/;

module.exports = grammar({
  name: "diff",

  extras: ($) => [WHITE_SPACE],

  rules: {
    source: ($) =>
      seq(
        repeat(choice($.block, seq(optional($._line), NEWLINE))),
        optional($._line)
      ),

    _line: ($) =>
      choice(
        $.file_change,
        $.binary_change,
        $.index,
        $.similarity,
        $.old_file,
        $.new_file,
        $.location,
        $.addition,
        $.deletion,
        $.context,
        $.comment
      ),

    block: ($) =>
      prec.right(
        seq(
          $.command,
          NEWLINE,
          repeat(
            seq(
              choice($.file_change, $.binary_change, $.index, $.similarity),
              NEWLINE
            )
          ),
          optional(seq($.old_file, NEWLINE, $.new_file, NEWLINE, $.hunks))
        )
      ),

    hunks: ($) => prec.right(repeat1($.hunk)),

    hunk: ($) =>
      prec.right(
        seq(
          field("location", $.location),
          NEWLINE,
          optional(field("changes", $.changes))
        )
      ),

    changes: ($) =>
      prec.right(
        repeat1(
          seq(
            choice($.addition, $.deletion, $.context),
            prec.right(repeat1(NEWLINE))
          )
        )
      ),

    command: ($) => iseq("diff", alias(/[-\w]+/, $.argument), $.filename),

    file_change: ($) =>
      choice(
        seq(choice("new", "deleted"), "file", "mode", $.mode),
        seq(choice("new", "old"), "mode", $.mode),
        seq("rename", choice("from", "to"), $.filename)
      ),

    binary_change: ($) =>
      iseq("Binary", "files", $.filename, "and", $.filename, "differ"),

    index: ($) => iseq("index", $.commit, "..", $.commit, optional($.mode)),

    similarity: ($) => iseq("similarity", "index", alias(/\d+/, $.score), "%"),

    old_file: ($) => iseq("---", $.filename),
    new_file: ($) => iseq("+++", $.filename),

    location: ($) =>
      iseq("@@", $.linerange, $.linerange, "@@", optional(ANYTHING)),

    addition: ($) =>
      choice(
        iseq("+", optional(ANYTHING)),
        iseq("++", optional(ANYTHING)),
        iseq("+++"),
        iseq("++++", optional(ANYTHING))
      ),
    deletion: ($) =>
      choice(
        iseq("-", optional(ANYTHING)),
        iseq("--", optional(ANYTHING)),
        iseq("---"),
        iseq("----", optional(ANYTHING))
      ),

    context: ($) => token(prec(-1, ANYTHING)),
    comment: ($) => iseq("#", optional(ANYTHING)),

    linerange: ($) => /[-\+]\d+(,\d+)?/,
    filename: ($) => repeat1(/\S+/),
    commit: ($) => /[a-f0-9]{7,40}/,
    mode: ($) => /\d+/,
  },
});

function iseq(start_token, ...tokens) {
  return seq(token.immediate(start_token), ...tokens);
}