// https://golang.org/ref/mod#go-mod-file-grammar
module.exports = grammar({
  name: "gomod",

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

  rules: {
    source_file: ($) => repeat($._directive),

    _directive: ($) =>
      choice(
        $.module_directive,
        $.go_directive,
        $.toolchain_directive,
        $.require_directive,
        $.exclude_directive,
        $.replace_directive,
        $.retract_directive
      ),

    _string_literal: ($) =>
      choice($.raw_string_literal, $.interpreted_string_literal),

    raw_string_literal: ($) => token(seq("`", repeat(/[^`]/), "`")),

    interpreted_string_literal: ($) =>
      seq(
        '"',
        repeat(
          choice(token.immediate(prec(1, /[^"\n\\]+/)), $.escape_sequence)
        ),
        '"'
      ),

    escape_sequence: ($) =>
      token.immediate(
        seq(
          "\\",
          choice(
            /[^xuU]/,
            /\d{2,3}/,
            /x[0-9a-fA-F]{2,}/,
            /u[0-9a-fA-F]{4}/,
            /U[0-9a-fA-F]{8}/
          )
        )
      ),

    _identifier: ($) => token(/[^\s,\[\]]+/),

    _string_or_ident: ($) => choice($._string_literal, $._identifier),

    module_path: ($) => $._string_or_ident,
    go_version: ($) => $._string_or_ident,
    version: ($) => $._string_or_ident,

    module_directive: ($) =>
      seq(
        "module",
        choice($.module_path, seq("(", "\n", $.module_path, "\n", ")"))
      ),

    go_directive: ($) => seq("go", $.go_version, "\n"),

    toolchain_directive: ($) =>
      seq(
        "toolchain",
        field('name', $.toolchain_name),
      ),

    toolchain_name: ($) => $._string_or_ident,

    require_directive: ($) =>
      seq(
        "require",
        choice(
          $.require_spec,
          seq("(", "\n", repeat($.require_spec), ")", "\n")
        )
      ),

    require_spec: ($) => seq($.module_path, $.version, "\n"),

    exclude_directive: ($) =>
      seq(
        "exclude",
        choice(
          $.exclude_spec,
          seq("(", "\n", repeat($.exclude_spec), ")", "\n")
        )
      ),

    exclude_spec: ($) => seq($.module_path, $.version, "\n"),

    replace_directive: ($) =>
      seq(
        "replace",
        choice(
          $.replace_spec,
          seq("(", "\n", repeat($.replace_spec), ")", "\n")
        )
      ),

    replace_spec: ($) =>
      choice(
        seq($.module_path, optional($.version), "=>", $.file_path, "\n"),
        seq(
          $.module_path,
          optional($.version),
          "=>",
          $.module_path,
          $.version,
          "\n"
        )
      ),

    file_path: ($) => $._identifier,

    retract_directive: ($) =>
      seq(
        "retract",
        choice(
          seq("(", "\n", repeat($.retract_spec), ")", "\n"),
          $.retract_spec
        )
      ),

    retract_spec: ($) =>
      seq(
        choice(seq("[", $.version, prec(1, ","), $.version, "]"), $.version),
        "\n"
      ),

    comment: ($) => seq("//", /.*/),
  },
});
