соответствие сиделки для подобных сооружений

#grammar #treesitter

Вопрос:

Я пытаюсь создать сиделку на дереве для грамматики функций Minecraft.

Структура языка выглядит следующим образом:

 command @e[key=value] other args
 

У меня возникла проблема со значением во втором аргументе (целевой селектор) в приведенном выше примере. Это значение может быть многими вещами, такими как строки, числа, логические значения и две аналогичные структуры объектов (NBT и объект табло).

Вот примеры каждого:

НБТ

 {key:value}
 

Объект Табло

 {key=number} // where number is: N, ..N, N.., or N..N
 

Мой файл грамматики содержит следующий код:

 // unrelated code removed

module.exports = grammar({
  name: "mcfunction",
  rules: {
    root: $ => repeat(
      choice(
        $.command
      )
    ),
    command: $ => prec.right(seq(
      field("command_name", $.identifier),
      repeat(
        choice(
          $.selector
        )
      ),
      "n"
    )),
    identifier: $ => /[A-Za-z][w-] /,
    number: $ => prec(1, /-?d (.d )?/),
    boolean: $ => choice(
      "true",
      "false"
    ),
    string: $ => seq(
      """,
      repeat(
        choice(
          $._escape_sequence,
          /[^"]/
        )
      ),
      """
    ),
    _escape_sequence: $ => seq("\", """),
    selector: $ => seq(
      token(
        seq(
          "@",
          choice(
            "p", "a", "e", "s", "r"
          )
        )
      ),
      optional(
        seq(
          token.immediate("["),
          optional(
            repeat(
              seq(
                $.selector_option,
                optional(",")
              )
            )
          ),
          "]"
        )
      ),
    ),
    selector_option: $ => seq(
      $.selector_key,
      "=",
      $.selector_value
    ),
    selector_key: $ => /[a-z_-] /,
    selector_value: $ => choice(
      $.item,
      $.path,
      $.selector_key,
      $.selector_number,
      $.number,
      $.boolean,
      $.selector_object
    ),
    selector_number: $ => prec.right(1, choice(
      seq(
        "..",
        $.number
      ),
      seq(
        $.number,
        "..",
        $.number
      ),
      seq(
        $.number,
        ".."
      ),
      $.number
    )),
    selector_object: $ => choice(
      seq(
        "{",
        repeat(
          seq(
            $.selector_score,
            optional(",")
          )
        ),
        "}"
      ),
      seq(
        "{",
        repeat(
          seq(
            $.selector_nbt,
            optional(",")
          )
        ),
        "}"
      )
    ),
    selector_nbt: $ => seq(
      $.nbt_object_key,
      ":",
      $.nbt_object_value
    ),
    selector_score: $ => seq(
      field("selector_score_key", $.selector_key),
      "=",
      field("selector_score_value", $.selector_number)
    ),
    _namespace: $ => /[a-z_-] :/,
    item: $ => seq(
      $._namespace,
      $.selector_key
    ),
    path: $ => seq(
      choice($.item, /[a-z_] /),
      repeat1(
        token("/", /[a-z_]/)
      )
    ),
    nbt: $ => choice(
      $.nbt_array,
      $.nbt_object
    ),
    nbt_object: $ => seq(
      "{",
      repeat(
        seq(
          $.nbt_object_key,
          ":",
          $.nbt_object_value,
          optional(",")
        )
      ),
      "}"
    ),
    nbt_array: $ => seq(
      "[",
      repeat(
        seq(
          $.nbt_object_value,
          optional(",")
        )
      ),
      "]"
    ),
    nbt_object_key: $ => choice(
      $.string,
      $.number,
      $.identifier
    ),
    nbt_object_value: $ => choice(
      $.string,
      $.nbt_number,
      $.boolean,
      $.nbt
    ),
    nbt_number: $ => seq(
      $.number,
      field("nbt_number_suffix", optional(choice("l","s","d","f","b")))
    )
  }
});
 

Однако, если я скомпилирую и проанализирую test @e[scores={example=1..}] , я получу:

 (root [0, 0] - [6, 0]
  (command [0, 0] - [1, 0]
    command_name: (identifier [0, 0] - [0, 4])
    (selector [0, 5] - [0, 29]
      (selector_option [0, 8] - [0, 28]
        (selector_key [0, 8] - [0, 14])
        (selector_value [0, 15] - [0, 28]
          (selector_object [0, 15] - [0, 28]
            (ERROR [0, 16] - [0, 27]
              (nbt_object_key [0, 16] - [0, 23]
                (identifier [0, 16] - [0, 23]))))))))
tests/test.mcfunction  0 ms    (ERROR [0, 16] - [0, 27])
 

Ожидалось: вместо ERROR этого должно быть selector_score , и должно быть score_key и score_value .

Этого не произойдет, если я удалю selector_nbt последовательность из selector_object . Однако при выполнении синтаксического анализа (с обеими последовательностями или только selector_nbt ) по команде с использованием данных nbt ошибок нет.

Что я делаю не так?

Ответ №1:

Я решил эту проблему, используя один choice из двух конфликтующих ключей, что-то вроде этого:

 choice(
  alias($.key_1, $.key_2),
  $.key_2
)
 

ahlinc на GitHub ответил:

Вы можете исправить свою ошибку в приведенной выше грамматике, присвоив терминалу приоритет лексера selector_key над identifier терминалом, например:

 selector_key: $ => token(prec(1, /[a-z_-] /)),
 

Но вы должны отметить, что вы используете регулярные выражения, которые конфликтуют:

 identifier: $ => /[A-Za-z][w-] /,
selector_key: $ => token(prec(1, /[a-z_-] /)),
 

Если невозможно переписать приведенные выше регулярные выражения, чтобы в них не было конфликтов, вам может потребоваться обходной путь, описанный здесь: #1287 (ответ в потоке)