; SPDX-FileCopyrightText: 2023 Leorize <leorize+oss@disroot.org>, aMOPel <>
; SPDX-License-Identifier: MPL-2.0
; SPDX-License-Identifier: Apache-2.0
; =============================================================================
; catch all rules
((identifier) @variable
  (#set! priority 99))

; NOTE: we need priority, since (identifier) is most specific and we have to
; capture nodes containing (identifier) as a whole, while overruling the
; @variable capture.
(type_expression) @type

; NOTE: has to be after
; ((identifier) @variable (#set! priority 99))
; overrule identifiers in pragmas in (proc_type)s and (pragma_expression)s
(proc_type
  pragmas: (pragma_list) @variable)

(iterator_type
  pragmas: (pragma_list) @variable)

(type_expression
  (pragma_expression
    right: (pragma_list) @variable))

; NOTE: has to be after
; (type_expression) @type
; and before @keyword.directive and all literals
; constants/enums in array construction
(array_construction
  (colon_expression
    left: (_) @constant))

; NOTE: has to be before literals and punctuation etc.
; identifiers in "case" "of" branches have to be enums
(case
  alternative: (of_branch
    values: (expression_list
      (_) @constant)))

; NOTE: has to be before literals and punctuation etc.
; in variant objects with "case" "of"
(variant_declaration
  alternative: (of_branch
    values: (expression_list
      (_) @constant)))

; NOTE: has to be before literals and punctuation etc.
; =============================================================================
; @comment               ; line and block comments
[
  (comment)
  (block_comment)
] @comment

; =============================================================================
; @comment.documentation ; comments documenting code
(documentation_comment
  "##" @comment.documentation)

(block_documentation_comment
  "##[" @comment.documentation
  "]##" @comment.documentation)

; NOTE: leaving content uncaptured so markdown can be injected
; =============================================================================
; @punctuation.delimiter ; delimiters (e.g. `;` / `.` / `,`)
[
  "."
  ";"
  ","
  ":"
  "="
] @punctuation.delimiter

; =============================================================================
; @operator              ; symbolic operators (e.g. `+` / `*`)
(operator) @operator

(assignment
  "=" @operator)

; =============================================================================
; @punctuation.bracket   ; brackets (e.g. `()` / `{}` / `[]`)
[
  "("
  ")"
  "["
  "[:"
  "]"
  "{"
  "}"
] @punctuation.bracket

; =============================================================================
; @keyword.directive               ; various preprocessor directives & shebangs
[
  "macro"
  "template"
] @keyword.directive

(pragma_list
  [
    "{."
    "}"
    ".}"
  ] @keyword.directive)

; NOTE: has to come after @punctuation.bracket
; =============================================================================
; @punctuation.special   ; special symbols (e.g. `{}` in string interpolation)
(accent_quoted
  "`" @punctuation.special)

(exported_symbol
  "*" @punctuation.special)

; dereference operator
(bracket_expression
  !right
  "[" @punctuation.special
  .
  "]" @punctuation.special)

; =============================================================================
; @string               ; string literals
[
  (interpreted_string_literal)
  (long_string_literal)
  (raw_string_literal)
  (generalized_string)
] @string

; injections in generalized_strings
(generalized_string
  (string_content) @none)

; format string injection in normal strings with & prefix
(prefix_expression
  operator: (operator) @_string_prefix
  .
  (_
    (string_content) @none)
  (#eq? @_string_prefix "&"))

; emit pragma injection
(pragma_statement
  (pragma_list
    (colon_expression
      left: (identifier) @_emit_keyword
      (#eq? @_emit_keyword "emit")
      right: (_
        (string_content) @none))))

; =============================================================================
; @string.escape        ; escape sequences
(escape_sequence) @string.escape

; =============================================================================
; @character            ; character literals
(char_literal) @character

; =============================================================================
; @boolean              ; boolean literals
((identifier) @boolean
  (#any-of? @boolean "true" "false" "on" "off"))

; =============================================================================
; @number               ; numeric literals
(integer_literal) @number

(custom_numeric_literal) @number

; =============================================================================
; @number.float                ; floating-point number literals
(float_literal) @number.float

; =============================================================================
; @function         ; function definitions
(proc_declaration
  name: [
    (identifier) @function
    (accent_quoted
      (identifier) @function)
    (exported_symbol
      (identifier) @function)
    (exported_symbol
      (accent_quoted
        (identifier) @function))
  ])

(func_declaration
  name: [
    (identifier) @function
    (accent_quoted
      (identifier) @function)
    (exported_symbol
      (identifier) @function)
    (exported_symbol
      (accent_quoted
        (identifier) @function))
  ])

(iterator_declaration
  name: [
    (identifier) @function
    (accent_quoted
      (identifier) @function)
    (exported_symbol
      (identifier) @function)
    (exported_symbol
      (accent_quoted
        (identifier) @function))
  ])

(converter_declaration
  name: [
    (identifier) @function
    (accent_quoted
      (identifier) @function)
    (exported_symbol
      (identifier) @function)
    (exported_symbol
      (accent_quoted
        (identifier) @function))
  ])

; =============================================================================
; @function.call    ; function calls
(call
  function: [
    (identifier) @function.call
    (accent_quoted
      (identifier) @function.call)
    ; generic types
    (bracket_expression
      left: (identifier) @function.call)
    (bracket_expression
      left: (accent_quoted
        (identifier) @function.call))
    ; dot accessor
    (dot_expression
      right: (identifier) @function.call)
    (dot_expression
      right: (accent_quoted
        (identifier) @function.call))
    ; both
    (bracket_expression
      left: (dot_expression
        right: (identifier) @function.call))
    (bracket_expression
      left: (dot_expression
        right: (accent_quoted
          (identifier) @function.call)))
  ])

(dot_generic_call
  function: [
    (identifier) @function.call
    (accent_quoted
      (identifier) @function.call)
  ])

; generalized_string is a function call
; `identifier"string literal"`
; is short for
; `identifier(r"string literal")`
(generalized_string
  function: [
    (identifier) @function.call
    (accent_quoted
      (identifier) @function.call)
  ])

; call with leading literal
(dot_expression
  left: [
    (nil_literal)
    (integer_literal)
    (float_literal)
    (custom_numeric_literal)
    (char_literal)
    (interpreted_string_literal)
    (long_string_literal)
    (raw_string_literal)
    (generalized_string)
    (array_construction)
    ; for sequences
    (prefix_expression
      operator: (operator) @_at
      (array_construction)
      (#eq? @_at "@"))
    (tuple_construction)
    (curly_construction)
  ]
  right: [
    (identifier) @function.call
    (accent_quoted
      (identifier) @function.call)
  ])

; NOTE: will double capture as @function.call if it also has argument_list
; function.calls in `varargs[type, routine]`
(bracket_expression
  left: (identifier) @_varargs
  right: (argument_list
    .
    (_)
    .
    [
      (identifier) @function.call
      (accent_quoted
        (identifier) @function.call)
      (dot_expression
        right: (identifier) @function.call)
      (dot_expression
        right: (accent_quoted
          (identifier) @function.call))
    ])
  (#eq? @_varargs "varargs"))

; =============================================================================
; @function.macro   ; preprocessor macros
(template_declaration
  name: [
    (identifier) @function.macro
    (accent_quoted
      (identifier) @function.macro)
    (exported_symbol
      (identifier) @function.macro)
    (exported_symbol
      (accent_quoted
        (identifier) @function.macro))
  ])

(macro_declaration
  name: [
    (identifier) @function.macro
    (accent_quoted
      (identifier) @function.macro)
    (exported_symbol
      (identifier) @function.macro)
    (exported_symbol
      (accent_quoted
        (identifier) @function.macro))
  ])

; =============================================================================
; @function.method           ; method definitions
(method_declaration
  name: [
    (identifier) @function.method
    (accent_quoted
      (identifier) @function.method)
    (exported_symbol
      (identifier) @function.method)
    (exported_symbol
      (accent_quoted
        (identifier) @function.method))
  ])

; =============================================================================
; @constructor      ; constructor calls and definitions
(call
  function: [
    (identifier) @constructor
    (accent_quoted
      (identifier) @constructor)
    ; generic types
    (bracket_expression
      left: (identifier) @constructor)
    (bracket_expression
      left: (accent_quoted
        (identifier) @constructor))
    ; dot accessor
    (dot_expression
      right: (identifier) @constructor)
    (dot_expression
      right: (accent_quoted
        (identifier) @constructor))
    ; both
    (bracket_expression
      left: (dot_expression
        right: (identifier) @constructor))
    (bracket_expression
      left: (dot_expression
        right: (accent_quoted
          (identifier) @constructor)))
  ]
  (argument_list
    (colon_expression)+))

; NOTE: this cannot detect constructors with 0 arguments
; those will be matched as @function.call instead
; =============================================================================
; @keyword             ; various keywords
; unhandled but reserved keywords
; end
; interface
; static expression
; addr operator
((call
  function: (identifier) @keyword)
  (#any-of? @keyword "static" "addr"))

[
  "const"
  "let"
  "var"
  "concept"
  "asm"
  "bind"
  "defer"
  "do"
  "mixin"
  "static"
  "tuple"
  "block"
  "using"
  "discard"
] @keyword

[
  "enum"
  "object"
  "type"
] @keyword.type

; =============================================================================
; @keyword.function    ; keywords that define a function (e.g. `func` in Go, `def` in Python)
[
  "proc"
  "func"
  "method"
  "converter"
  "iterator"
] @keyword.function

; =============================================================================
; @keyword.operator    ; operators that are English words (e.g. `and` / `or`)
[
  "and"
  "or"
  "xor"
  "not"
  "div"
  "mod"
  "shl"
  "shr"
  "from"
  "as"
  "of"
  "in"
  "notin"
  "is"
  "isnot"
  "cast"
] @keyword.operator

; =============================================================================
; @keyword.return      ; keywords like `return` and `yield`
[
  "return"
  "yield"
] @keyword.return

; =============================================================================
; @keyword.conditional         ; keywords related to conditionals (e.g. `if` / `else`)
[
  "if"
  "when"
  "case"
  "elif"
  "else"
] @keyword.conditional

(of_branch
  "of" @keyword.conditional)

; =============================================================================
; @keyword.repeat              ; keywords related to loops (e.g. `for` / `while`)
[
  "for"
  "while"
  "continue"
  "break"
] @keyword.repeat

(for
  "in" @keyword.repeat)

; =============================================================================
; @label               ; GOTO and other labels (e.g. `label:` in C)
(block
  label: [
    (identifier) @label
    (accent_quoted
      (identifier) @label)
  ])

; =============================================================================
; @keyword.import             ; keywords for including modules (e.g. `import` / `from` in Python)
[
  "import"
  "include"
  "export"
] @keyword.import

(import_from_statement
  "from" @keyword.import)

(except_clause
  "except" @keyword.import)

; =============================================================================
; @keyword.exception           ; keywords related to exceptions (e.g. `throw` / `catch`)
[
  "try"
  "except"
  "finally"
  "raise"
] @keyword.exception

; =============================================================================
; @type            ; type or class definitions and annotations
; generic types when calling
(call
  function: (bracket_expression
    right: (argument_list) @type))

; NOTE: this also falsely matches
; when accessing and directly call elements from an array of routines
; eg `array_of_routines[index](arguments), but that is an uncommon case
; dot_generic_call `v.call[:type, type]()
(dot_generic_call
  generic_arguments: (_) @type)

; right side of `is` operator is always type
(infix_expression
  operator: [
    "is"
    "isnot"
  ]
  right: (_) @type)

; except branch always contains types of errors
; Eg: `except module.exception[gen_type]:`
(except_branch
  values: (expression_list) @type)

; overrule special case in (except_branch) with "as" operator
; `except module.exception[gen_type] as variable:`
(except_branch
  values: (expression_list
    (infix_expression
      operator: "as"
      right: [
        (identifier) @variable
        (accent_quoted
          (identifier) @variable)
      ])))

; for inline tuple types
; `type a = tuple[a: int]`
(tuple_type
  "tuple" @type
  (field_declaration_list))

; NOTE: this is consistent with other generic types like `seq[int]`
; but inconsistent with multiline tuple declaration,
; where `tuple` is captured as @keyword
; =============================================================================
; @variable.parameter        ; parameters of a function
; named parameters when calling
; call(parameter_name=arg)
(argument_list
  (equal_expression
    left: [
      (identifier) @variable.parameter
      (accent_quoted
        (identifier) @variable.parameter)
    ]))

; parameters in function declaration
(parameter_declaration_list
  (parameter_declaration
    (symbol_declaration_list
      (symbol_declaration
        name: [
          (identifier) @variable.parameter
          (accent_quoted
            (identifier) @variable.parameter)
        ]))))

; NOTE: needs to be after @type
; generic types when declaring
(generic_parameter_list
  (parameter_declaration
    (symbol_declaration_list
      (symbol_declaration
        name: [
          (identifier) @variable.parameter
          (accent_quoted
            (identifier) @variable.parameter)
        ]))))

; for loop variables
(for
  left: (symbol_declaration_list
    (symbol_declaration
      name: [
        (identifier) @variable.parameter
        (accent_quoted
          (identifier) @variable.parameter)
      ])))

((tuple_deconstruct_declaration
  (symbol_declaration
    name: [
      (identifier) @variable.parameter
      (accent_quoted
        (identifier) @variable.parameter)
    ])) @_tuple_decons
  (#has-ancestor? @_tuple_decons for))

(concept_declaration
  parameters: (parameter_list
    [
      (identifier) @variable.parameter
      (accent_quoted
        (identifier) @variable.parameter)
    ]))

(var_parameter
  [
    (identifier) @variable.parameter
    (accent_quoted
      (identifier) @variable.parameter)
  ])

(type_parameter
  [
    (identifier) @variable.parameter
    (accent_quoted
      (identifier) @variable.parameter)
  ])

(static_parameter
  [
    (identifier) @variable.parameter
    (accent_quoted
      (identifier) @variable.parameter)
  ])

(ref_parameter
  [
    (identifier) @variable.parameter
    (accent_quoted
      (identifier) @variable.parameter)
  ])

(pointer_parameter
  [
    (identifier) @variable.parameter
    (accent_quoted
      (identifier) @variable.parameter)
  ])

; =============================================================================
; @type.definition ; type definitions (e.g. `typedef` in C)
(type_section
  (type_declaration
    (type_symbol_declaration
      name: [
        (identifier) @type.definition
        (accent_quoted
          (identifier) @type.definition)
        (exported_symbol
          (identifier) @type.definition)
        (exported_symbol
          (accent_quoted
            (identifier) @type.definition))
      ])))

; =============================================================================
; @keyword.modifier  ; type qualifier keywords (e.g. `const`)
(var_type
  "var" @keyword.modifier)

(out_type
  "out" @keyword.modifier)

(distinct_type
  "distinct" @keyword.modifier)

(ref_type
  "ref" @keyword.modifier)

(pointer_type
  "ptr" @keyword.modifier)

(var_parameter
  "var" @keyword.modifier)

(type_parameter
  "type" @keyword.modifier)

(static_parameter
  "static" @keyword.modifier)

(ref_parameter
  "ref" @keyword.modifier)

(pointer_parameter
  "ptr" @keyword.modifier)

; =============================================================================
; @variable.member           ; object and struct fields
; fields in object/tuple declaration
(field_declaration
  (symbol_declaration_list
    (symbol_declaration
      name: [
        (identifier) @variable.member
        (accent_quoted
          (identifier) @variable.member)
        (exported_symbol
          (identifier) @variable.member)
        (exported_symbol
          (accent_quoted
            (identifier) @variable.member))
      ])))

; fields in object construction
(call
  (argument_list
    (colon_expression
      left: [
        (identifier) @variable.member
        (accent_quoted
          (identifier) @variable.member)
      ])))

; fields in tuple construction
(tuple_construction
  (colon_expression
    left: [
      (identifier) @variable.member
      (accent_quoted
        (identifier) @variable.member)
    ]))

(variant_declaration
  (variant_discriminator_declaration
    (symbol_declaration_list
      (symbol_declaration
        name: [
          (identifier) @variable.member
          (accent_quoted
            (identifier) @variable.member)
          (exported_symbol
            (identifier) @variable.member)
          (exported_symbol
            (accent_quoted
              (identifier) @variable.member))
        ]))))

; =============================================================================
; @variable.builtin ; built-in variable names (e.g. `this`)
(blank_identifier) @variable.builtin

((identifier) @variable.builtin
  (#eq? @variable.builtin "result"))

; NOTE: technically needs `has-ancestor some routine declaration` but it's
; not worth it
; =============================================================================
; @constant         ; constant identifiers
; enum declaration
(enum_field_declaration
  (symbol_declaration
    name: [
      (identifier) @constant
      (accent_quoted
        (identifier) @constant)
    ]))

; constant declaration
(const_section
  (variable_declaration
    (symbol_declaration_list
      (symbol_declaration
        name: [
          (identifier) @constant
          (accent_quoted
            (identifier) @constant)
          (exported_symbol
            (identifier) @constant)
          (exported_symbol
            (accent_quoted
              (identifier) @constant))
        ]))))

((tuple_deconstruct_declaration
  (symbol_declaration
    name: [
      (identifier) @constant
      (accent_quoted
        (identifier) @constant)
      (exported_symbol
        (identifier) @constant)
      (exported_symbol
        (accent_quoted
          (identifier) @constant))
    ])) @_tuple_decons
  (#has-ancestor? @_tuple_decons const_section))

; constants x and y in `array[x..y, type]`
(bracket_expression
  left: (identifier) @_array
  right: (argument_list
    .
    (infix_expression
      right: [
        (identifier) @constant
        (accent_quoted
          (identifier) @constant)
        (dot_expression
          right: (identifier) @constant)
        (dot_expression
          right: (accent_quoted
            (identifier) @constant))
      ]))
  (#any-of? @_array "array" "range"))

(bracket_expression
  left: (identifier) @_array
  right: (argument_list
    .
    (infix_expression
      left: [
        (identifier) @constant
        (accent_quoted
          (identifier) @constant)
        (dot_expression
          right: (identifier) @constant)
        (dot_expression
          right: (accent_quoted
            (identifier) @constant))
      ]))
  (#any-of? @_array "array" "range"))

; NOTE: can only do this for (infix_expression)s, since standalone identifiers
; could be ordinal types and constants
; =============================================================================
; @constant.builtin ; built-in constant values
(nil_literal) @constant.builtin

; =============================================================================
; @module        ; modules or namespaces
(import_statement
  (expression_list
    (identifier) @module))

(import_statement
  (expression_list
    (infix_expression
      operator: "as"
      right: (identifier) @module)))

(import_statement
  (expression_list
    (infix_expression
      operator: (operator) @_operator
      right: [
        (identifier) @module
        (array_construction
          (identifier) @module)
      ]))
  (#eq? @_operator "/"))

(import_from_statement
  module: (infix_expression
    operator: (operator) @_operator
    right: (identifier) @module)
  (#eq? @_operator "/"))

(export_statement
  (expression_list
    (identifier) @module))

; =============================================================================
; overrule things
; left identifier in dot_expression
(dot_expression
  left: [
    (identifier) @none
    (accent_quoted
      (identifier) @none)
  ])

; NOTE: we can't know what the left identifier is, so better leave it alone
; for consistency
; discard literals is like a comment
(discard_statement
  "discard" @comment
  [
    (nil_literal)
    (integer_literal)
    (float_literal)
    (custom_numeric_literal)
    (char_literal)
    (interpreted_string_literal)
    (long_string_literal)
    (raw_string_literal)
    (generalized_string)
    (array_construction)
    ; for sequences
    (prefix_expression
      operator: (operator) @_at
      (array_construction)
      (#eq? @_at "@"))
    (tuple_construction)
    (curly_construction)
  ] @comment)
