tree-sitter-query 0.1.0

query grammar for the tree-sitter parsing library
Documentation
const PREC = {
  IMMEDIATE_CHILD: 1,

  // Prefer a string over a comment
  COMMENT: 1,
  STRING: 2,

  WILDCARD_NODE: 1,
};

const IDENTIFIER = /[a-zA-Z0-9.\-_\$]+/;

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

  extras: $ => [
    $.comment,
    /\s+/,
  ],

  rules: {
    program: $ => repeat($._definition),

    _definition: $ => choice(
      $.named_node,
      $.anonymous_node,
      $.grouping,
      $.predicate,
      $.list,
      $.field_definition,
    ),

    // Expressions that are valid inside a group.
    _group_expression: $ => choice(
      $._definition,
      immediate_child($._group_expression),
    ),

    // Expressions that are valid inside a named node.
    _named_node_expression: $ => choice(
      $._definition,
      $.negated_field,
      immediate_child($._named_node_expression),
    ),

    _string: $ => seq(
      '"',
      repeat(choice(
        token.immediate(prec(PREC.STRING, /[^"\\]+/)),
        $.escape_sequence,
      )),
      '"',
    ),
    // Taken from https://github.com/tree-sitter/tree-sitter-javascript/blob/3f8b62f9befd3cb3b4cb0de22f6595a0aadf76ca/grammar.js#L827
    escape_sequence: $ => token.immediate(seq(
      '\\',
      choice(
        /[^xu0-7]/,
        /[0-7]{1,3}/,
        /x[0-9a-fA-F]{2}/,
        /u[0-9a-fA-F]{4}/,
        /u{[0-9a-fA-F]+}/
      )
    )),

    quantifier: $ => choice("*", "+", "?"),

    identifier: $ => IDENTIFIER,
    _immediate_identifier: $ => alias(token.immediate(IDENTIFIER), $.identifier),
    _node_identifier: $ => choice($.identifier, prec(PREC.WILDCARD_NODE, "_")),
    capture: $ => seq("@", field("name", $._immediate_identifier)),
    string: $ => $._string,
    parameters: $ => repeat1(choice($.capture, $.string, $._node_identifier)),
    comment: $ => token(prec(PREC.COMMENT, seq(";", /.*/))),
    list: $ => seq("[", repeat($._definition), "]", quantifier($), captures($)),

    grouping: $ => seq(
      "(",
      repeat($._group_expression),
      ")",
      quantifier($),
      captures($),
    ),

    anonymous_node: $ => seq(
      field("name", choice(alias($._string, $.identifier), "_")),
      quantifier($),
      captures($),
    ),

    named_node: $ => seq(
      "(",
      field("name", $._node_identifier),
      optional(
        seq(
          optional("."),
          choice(
            repeat1($._named_node_expression),
            seq(
              repeat($._named_node_expression),
              seq($._named_node_expression, "."),
            )
          ),
        ),
      ),
      ")",
      quantifier($),
      captures($)
    ),
    _field_name: $ => seq($.identifier, ":"),
    field_definition: $ => seq(
      field("name", $._field_name),
      $._definition,
    ),

    negated_field: $ => seq("!", $.identifier),

    predicate: $ =>
      seq(
        "(",
        field("name", seq("#", $._immediate_identifier, field("type", $.predicate_type))),
        field("parameters", $.parameters),
        ")"
      ),
    predicate_type: $ => token.immediate(choice("?", "!")),
  }
});

function captures($) {
  return repeat($.capture);
}

function quantifier($) {
  return optional(field("quantifier", $.quantifier));
}

function immediate_child(expression) {
  return prec.left(
    PREC.IMMEDIATE_CHILD,
    seq(expression, ".", expression),
  );
}