%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html

name: Clojure
file_extensions:
  - clj
  - cljc
  - edn
scope: source.clojure

variables:
  non_symbol_chars: \s,;\(\)\[\]{}\"`~@\^\\
  non_symbol_start_chars: '{{non_symbol_chars}}\d#'':'
  non_number_chars: '{{non_symbol_chars}}#'''
  non_char_chars: '{{non_symbol_chars}}#'''
  atom: '[^{{non_symbol_chars}}]+'
  symbol: (?:/|[^{{non_symbol_start_chars}}][^{{non_symbol_chars}}]*)
  # Slightly too permissive
  keyword: (:):?[^:{{non_symbol_chars}}][^{{non_symbol_chars}}]*
  constant: (?:nil|true|false)(?=[{{non_symbol_chars}}])
  evil_octal: '[-+]?0\d+N?(?=[{{non_symbol_chars}}])'

  # Similar to the number regexps in the Clojure reader.
  # This doesn't include evil octals and dot-terminated decimals.
  sign: '[-+]?'
  exponent: (?:[eE]{{sign}}\d+)
  dec_integer: ({{sign}})(\d+)(N?)(?=[{{non_number_chars}}])
  hex_integer: ({{sign}})(0[Xx])(\h+)(N?)(?=[{{non_number_chars}}])
  other_integer: ({{sign}})((?:[2-9]|[1-9]\d+)[Rr])([0-9A-Za-z]+)(?=[{{non_number_chars}}])
  rational: ({{sign}})(\d+)(/)(\d+)(?=[{{non_number_chars}}])
  float: ({{sign}})(\d+)(?:(?:(\.)(\d+)({{exponent}})?|({{exponent}}))(M)?|(M))(?=[{{non_number_chars}}])

contexts:
  main:
    - include: match-expr

  match-expr:
    - include: match-noise
    - match: '[)\]}]'
      scope: invalid.illegal.clojure
    - match: \(
      scope: punctuation.section.parens.begin.clojure
      push: pop-list-head
    - match: \[
      scope: punctuation.section.brackets.begin.clojure
      push:
        - match: \]
          scope: punctuation.section.brackets.end.clojure
          pop: true
        - include: match-expr
    - match: '#?{'
      scope: punctuation.section.braces.begin.clojure
      push:
        - match: '}'
          scope: punctuation.section.braces.end.clojure
          pop: true
        - include: match-expr
    - match: '"'
      scope: punctuation.definition.string.begin.clojure
      push: pop-string-tail
    - match: '#'
      scope: keyword.operator.macro.clojure
      push: pop-dispatch-expr
    - match: '''|`|~|@'
      scope: keyword.operator.macro.clojure
      push: pop-expr
    - match: \\\S[^{{non_char_chars}}]*
      scope: constant.character.clojure
    - match: '{{constant}}'
      scope: constant.language.clojure
    - match: '{{keyword}}'
      scope: constant.other.keyword.clojure
      captures:
        1: punctuation.definition.keyword.clojure
    - match: '{{evil_octal}}'
      scope: invalid.deprecated.clojure
    - match: '{{dec_integer}}'
      scope: meta.number.integer.decimal.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.value.clojure
        3: constant.numeric.suffix.clojure
    - match: '{{hex_integer}}'
      scope: meta.number.integer.hexadecimal.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.base.clojure
        3: constant.numeric.value.clojure
        4: constant.numeric.suffix.clojure
    - match: '{{other_integer}}'
      scope: meta.number.integer.other.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.base.clojure
        3: constant.numeric.value.clojure
    - match: '{{rational}}'
      scope: meta.number.rational.decimal.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.value.clojure
        3: punctuation.separator.rational.clojure
        4: constant.numeric.value.clojure
    - match: '{{float}}'
      scope: meta.number.float.decimal.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.value.clojure
        3: punctuation.separator.decimal.clojure
        4: constant.numeric.value.clojure
        5: constant.numeric.value.exponent.clojure
        6: constant.numeric.value.exponent.clojure
        7: constant.numeric.suffix.clojure
        8: constant.numeric.suffix.clojure
    - match: '{{atom}}'

  pop-expr:
    - include: match-noise
    - match: '[)\]}]'
      scope: invalid.illegal.clojure
      pop: true
    - match: \(
      scope: punctuation.section.parens.begin.clojure
      set: pop-list-head
    - match: \[
      scope: punctuation.section.brackets.begin.clojure
      set:
        - match: \]
          scope: punctuation.section.brackets.end.clojure
          pop: true
        - include: match-expr
    - match: '#?{'
      scope: punctuation.section.braces.begin.clojure
      set:
        - match: '}'
          scope: punctuation.section.braces.end.clojure
          pop: true
        - include: match-expr
    - match: '"'
      scope: punctuation.definition.string.begin.clojure
      set: pop-string-tail
    - match: '#'
      scope: keyword.operator.macro.clojure
      set: pop-dispatch-expr
    - match: '''|`|~|@'
      scope: keyword.operator.macro.clojure
      set: pop-expr
    - match: '\\\S[^{{non_char_chars}}]*'
      scope: constant.character.clojure
      pop: true
    - match: '{{constant}}'
      scope: constant.language.clojure
      pop: true
    - match: '{{keyword}}'
      scope: constant.other.keyword.clojure
      captures:
        1: punctuation.definition.keyword.clojure
      pop: true
    - match: '{{evil_octal}}'
      scope: invalid.deprecated.clojure
      pop: true
    - match: '{{dec_integer}}'
      scope: meta.number.integer.decimal.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.value.clojure
        3: constant.numeric.suffix.clojure
      pop: true
    - match: '{{hex_integer}}'
      scope: meta.number.integer.hexadecimal.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.base.clojure
        3: constant.numeric.value.clojure
        4: constant.numeric.suffix.clojure
      pop: true
    - match: '{{other_integer}}'
      scope: meta.number.integer.other.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.base.clojure
        3: constant.numeric.value.clojure
      pop: true
    - match: '{{rational}}'
      scope: meta.number.rational.decimal.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.value.clojure
        3: punctuation.separator.rational.clojure
        4: constant.numeric.value.clojure
      pop: true
    - match: '{{float}}'
      scope: meta.number.float.decimal.clojure
      captures:
        1: punctuation.definition.numeric.sign.clojure
        2: constant.numeric.value.clojure
        3: punctuation.separator.decimal.clojure
        4: constant.numeric.value.clojure
        5: constant.numeric.value.exponent.clojure
        6: constant.numeric.value.exponent.clojure
        7: constant.numeric.suffix.clojure
        8: constant.numeric.suffix.clojure
      pop: true
    - match: '{{atom}}'
      pop: true

  match-noise:
    # Explicitly matching \s also captures end-of-line; this allows apostrophe
    # pairing to work at the last cursor position in a comment line.
    - match: (;+|#!)(.|\s)*
      scope: comment.line.clojure
      captures:
        1: punctuation.definition.comment
    - match: ','
      scope: punctuation.comma.clojure comment.punctuation.comma.clojure
    - match: \^
      scope: keyword.operator.macro.clojure
      push: pop-expr

  pop-dispatch-expr:
    - match: (?=\s)
      set: pop-dispatch-tag-expr
    - match: _
      scope: keyword.operator.macro.clojure
      set: pop-expr
    - match: '#'
      scope: keyword.operator.macro.clojure
      set: pop-symbolic-value
    - match: "'"
      scope: keyword.operator.macro.clojure
      pop: true
    - match: '"'
      scope: punctuation.definition.string.begin.clojure
      set: pop-regexp-tail
    - match: (?=[\^:])
      pop: true
    - match: (?=\S)
      set: pop-dispatch-tag-expr

  pop-dispatch-tag-expr:
    - include: match-noise
    - match: '{{symbol}}'
      scope: keyword.operator.macro.clojure
      pop: true
    - match: (?=\S)
      set: pop-expr

  pop-symbolic-value:
    - include: match-noise
    - match: '{{atom}}'
      scope: constant.other.symbolic.clojure
      pop: true
    - match: (?=\S)
      set: pop-expr

  pop-regexp-tail:
    - meta_scope: string.regexp.clojure
    - match: '"'
      scope: punctuation.definition.string.end.clojure
      pop: true
    - match: ''
      push: scope:source.regexp#base-literal
      with_prototype:
        - match: (?=")
          pop: true

  pop-string-tail:
    - meta_scope: string.quoted.double.clojure
    - match: \\.
      scope: constant.character.escape.clojure
    - match: '"'
      scope: punctuation.definition.string.end.clojure
      pop: true

  pop-list-head:
    - include: match-noise
    - match: defprotocol[^{{non_symbol_chars}}]*
      scope: storage.type.clojure
      set: pop-declare-protocol-list-tail
    - match: definterface[^{{non_symbol_chars}}]*
      scope: storage.type.clojure
      set: pop-declare-interface-list-tail
    - match: (?:deftype|defrecord)[^{{non_symbol_chars}}]*
      scope: storage.type.clojure
      set: pop-declare-type-list-tail
    - match: defmethod[^{{non_symbol_chars}}]*
      scope: storage.modifier.def.clojure
      set: pop-fn-list-tail
    - match: def[^{{non_symbol_chars}}]*
      scope: storage.modifier.def.clojure
      set: pop-declare-def-list-tail
    - match: (?:reify|proxy|extend-protocol|extend-type)[^{{non_symbol_chars}}]*
      scope: variable.function.clojure
      set: pop-type-list-tail
    - match: fn\*?(?=[{{non_symbol_chars}}])
      scope: storage.modifier.fn.clojure
      set: pop-fn-list-tail
    - match: declare[^{{non_symbol_chars}}]*
      scope: storage.modifier.declare.clojure
      set: pop-declare-list-tail
    - match: (?=\S)
      set: pop-invoke-list-tail

  pop-list-tail:
    - match: \)
      scope: punctuation.section.parens.end.clojure
      pop: true
    - include: match-expr

  pop-declare-protocol-list-tail:
    - include: match-constant-set-normal-list-tail
    - match: '{{symbol}}'
      scope: entity.name.type.clojure
      set: pop-protocol-list-tail
    - include: set-normal-list-tail

  pop-protocol-list-tail:
    - match: \)
      scope: punctuation.section.parens.end.clojure
      pop: true
    - match: \(
      scope: punctuation.section.parens.begin.clojure
      push: pop-declare-def-list-tail
    - include: match-expr

  pop-declare-interface-list-tail:
    - include: match-constant-set-normal-list-tail
    - match: '{{symbol}}'
      scope: entity.name.type.clojure
      set: pop-interface-list-tail
    - include: set-normal-list-tail

  pop-interface-list-tail:
    - match: \)
      scope: punctuation.section.parens.end.clojure
      pop: true
    - match: \(
      scope: punctuation.section.parens.begin.clojure
      push: pop-fn-list-tail
    - include: match-expr

  pop-declare-type-list-tail:
    - include: match-constant-set-normal-list-tail
    - match: '{{symbol}}'
      scope: entity.name.type.clojure
      set: pop-type-list-tail
    - include: set-normal-list-tail

  pop-type-list-tail:
    - match: \)
      scope: punctuation.section.parens.end.clojure
      pop: true
    - match: \(
      scope: punctuation.section.parens.begin.clojure
      push: pop-fn-list-tail
    - include: match-symbol-implemented-or-extended
    - include: match-expr

  pop-declare-def-list-tail:
    - include: match-constant-set-normal-list-tail
    - match: '{{symbol}}'
      scope: entity.name.function.clojure
      set: pop-list-tail
    - include: set-normal-list-tail

  pop-fn-list-tail:
    - include: match-constant-set-normal-list-tail
    - match: '{{symbol}}'
      scope: entity.name.function.fn.clojure
      set: pop-list-tail
    - include: set-normal-list-tail

  pop-declare-list-tail:
    - include: match-constant-set-normal-list-tail
    - match: '{{symbol}}'
      scope: entity.name.function.forward-decl.clojure
      set: pop-list-tail
    - include: set-normal-list-tail

  pop-invoke-list-tail:
    - include: match-constant-set-normal-list-tail
    - match: '{{symbol}}'
      scope: variable.function.clojure
      set: pop-list-tail
    - include: set-normal-list-tail

  match-constant-set-normal-list-tail:
    - include: match-noise
    - match: '{{constant}}'
      scope: constant.language.clojure
      set: pop-list-tail

  match-symbol-implemented-or-extended:
    - match: '{{constant}}'
      scope: constant.language.clojure
    - match: '{{symbol}}'
      scope: entity.other.inherited-class.clojure

  set-normal-list-tail:
    - match: (?=\S)
      set: pop-list-tail
