%YAML 1.2
---
name: Python
file_extensions:
  - py
  - py3
  - pyw
  - pyi
  - pyx
  - pyx.in
  - pxd
  - pxd.in
  - pxi
  - pxi.in
  - rpy
  - cpy
  - SConstruct
  - Sconstruct
  - sconstruct
  - SConscript
  - gyp
  - gypi
  - Snakefile
  - vpy
  - wscript
  - bazel
  - bzl
first_line_match: ^#!\s*/.*\bpython(\d(\.\d)?)?\b
scope: source.python

variables:
  # We support unicode here because Python 3 is the future
  identifier_continue: '[[:alnum:]_]'
  identifier: '\b[[:alpha:]_]{{identifier_continue}}*\b'
  identifier_constant: '\b(?:[\p{Lu}_][\p{Lu}_\d]*)?[\p{Lu}]{2,}[\p{Lu}_\d]*\b'  # require 2 consecutive upper-case letters
  digits: (?:\d+(?:_\d+)*)
  exponent: (?:[eE][-+]?{{digits}})
  path: '({{identifier}}[ ]*\.[ ]*)*{{identifier}}'
  sql_indicator: \s*(?:SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|WITH)\b
  illegal_names: (?:and|as|assert|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|not|or|pass|raise|return|try|while|with|yield)
  format_spec: |-
    (?x:
      (?:.? [<>=^])?     # fill align
      [ +-]?             # sign
      \#?                # alternate form
      # technically, octal and hexadecimal integers are also supported as 'width', but rarely used
      \d*                # width
      ,?                 # thousands separator
      (?:\.\d+)?         # precision
      [bcdeEfFgGnosxX%]? # type
    )
  strftime_spec: '(?:%(?:[aAwdbBGmyYHIpMSfzZjuUVWcxX%]|-[dmHIMSj]))'
  # This can be used in look-aheads to parse simple expressions.
  # Can't be recursive, because sregex doesn't support that,
  # so we're skipping parentheses.
  # Can't parse multiple lines as well, for obvious reasons
  simple_expression: |-
    (?x:
      \s+                      # whitespace
      | [urfb]*"(?:\\.|[^"])*" # strings
      | [urfb]*'(?:\\.|[^'])*' # ^
      | [\d.ej]+               # numerics
      | [+*/%@-] | // | and | or # operators
      | {{path}}               # a path
    )*


contexts:
  main:
    - include: statements

  statements:
    - include: docstrings
    - include: line-statements
    - include: block-statements
    - include: classes
    - include: functions
    - include: modifiers
    - include: assignments
    - match: ;
      scope: punctuation.terminator.statement.python
    - include: expression-as-a-statement

  line-statements:
    - include: imports
    - include: decorators
    - match: \b(raise)\b
      scope: keyword.control.flow.raise.python
      push:
        - meta_scope: meta.statement.raise.python
        - include: line-continuation-or-pop
        - match: \b(from)\b
          scope: keyword.control.flow.raise.from.python
          set:
            - meta_scope: meta.statement.raise.python
            - include: line-continuation-or-pop
            - include: expression-in-a-statement
        - include: expression-in-a-statement
    - match: \b(assert)\b
      scope: keyword.control.flow.assert.python
    - match: \b(del)\b
      scope: keyword.other.del.python
    - match: \b(print)\b(?! *([,.\]}]))
      scope: keyword.other.print.python
    - match: \b(exec)\b(?! *($|[,.()\]}]))
      scope: keyword.other.exec.python
    - match: \b(return)\b
      scope: keyword.control.flow.return.python
    - match: \b(break)\b
      scope: keyword.control.flow.break.python
    - match: \b(continue)\b
      scope: keyword.control.flow.continue.python
    - match: \b(pass)\b
      scope: keyword.control.flow.pass.python

  imports:
    - match: \b(import)\b
      scope: keyword.control.import.python
      push:
        - imports-import-body
        - expect-absolute-import
    - match: \b(from)\b
      scope: keyword.control.import.from.python
      push:
        - imports-from-body
        - maybe-relative-import

  imports-import-body:
    - meta_scope: meta.statement.import.python
    - include: line-continuation-or-pop
    - match: ','
      scope: punctuation.separator.import-list.python
      push: expect-absolute-import
    - match: (?=\bas\b)
      set: import-alias-list
    - include: qualified-name
    - match: (?=\S)
      pop: true

  imports-from-body:
    - meta_scope: meta.statement.import.python
    - meta_content_scope: meta.import-source.python
    - include: line-continuation-or-pop
    - match: (?=\bas\b)
      set: import-alias-list
    - match: (?=\bimport\b)
      set:
        - meta_include_prototype: false
        - match: import
          scope: keyword.control.import.python
          set: imports-from-import-body
    - match: '{{illegal_names}}\b'
      scope: meta.import-path.python invalid.illegal.name.python
    - match: '{{identifier}}'
      scope: meta.import-path.python meta.import-name.python
    - match: \s*(\.) *(?={{identifier}}|$)
      captures:
        0: meta.import-path.python
        1: punctuation.accessor.dot.python
    - match: \s*(\. *\S+) # matches and consumes the remainder of "abc.123" or "abc.+"
      captures:
        0: meta.import-path.python
        1: invalid.illegal.name.python
    - match: (?=\S)
      pop: true

  imports-from-import-body:
    - meta_scope: meta.statement.import.python
    - include: line-continuation-or-pop
    - match: (?=\()
      set:
        - meta_include_prototype: false
        - match: \(
          scope: punctuation.section.import-list.begin.python
          set:
            - meta_scope: meta.statement.import.python meta.import-list.python
            - match: \)
              scope: punctuation.section.import-list.end.python
              pop: true
            - include: comments
            - include: import-name-list
    - match: (?=\S)
      set: import-alias-list

  import-alias-list:
    - meta_content_scope: meta.statement.import.python
    - include: line-continuation-or-pop
    - include: import-name-list

  import-name-list:
    - match: ','
      scope: punctuation.separator.import-list.python
    - match: \*
      scope: constant.language.import-all.python
    - match: \b(as)\b
      scope: keyword.control.import.as.python
    - include: name
    - match: '[^\s,)]+'
      scope: invalid.illegal.name.import.python

  expect-absolute-import:
    - include: line-continuation-or-pop
    - match: \.+
      scope: invalid.illegal.unexpected-relative-import.python
    - match: (?=\S)
      pop: true

  maybe-relative-import:
    - include: line-continuation-or-pop
    - match: \.+
      scope: meta.import-path.python keyword.control.import.relative.python
    - match: (?=\S)
      pop: true

  block-statements:
    # async for ... in ...:
    - match: \b(async +)?(for)\b
      captures:
        1: storage.modifier.async.python
        2: keyword.control.loop.for.python
      push:
        - meta_scope: meta.statement.loop.for.python
        - include: line-continuation-or-pop
        - match: \bin\b
          scope: keyword.control.loop.for.in.python
          set:
            - meta_content_scope: meta.statement.loop.for.python
            - include: line-continuation-or-pop
            - match: ':(?!=)'
              scope: meta.statement.loop.for.python punctuation.section.block.loop.for.python
              pop: true
            - include: expression-in-a-statement
        - match: ':(?!=)'
          scope: invalid.illegal.missing-in.python
          pop: true
        - include: target-list
    # async with ... as ...:
    - match: \b(async +)?(with)\b
      captures:
        1: storage.modifier.async.python
        2: keyword.control.flow.with.python
      push: with-body
    # except ... as ...:
    - match: \bexcept\b
      scope: keyword.control.exception.catch.python
      push:
        - meta_scope: meta.statement.exception.catch.python
        - include: line-continuation-or-pop
        - match: ':(?!=)'
          scope: punctuation.section.block.exception.catch.python
          pop: true
        - match: '\bas\b'
          scope: keyword.control.exception.catch.as.python
          set:
            - meta_content_scope: meta.statement.exception.catch.python
            - include: line-continuation-or-pop
            - match: ':'
              scope: meta.statement.exception.catch.python punctuation.section.block.exception.catch.python
              pop: true
            - include: name
        - include: target-list
    - match: \bif\b
      scope: keyword.control.conditional.if.python
      push:
        - meta_scope: meta.statement.conditional.if.python
        - include: line-continuation-or-pop
        - match: ':(?!=)'
          scope: punctuation.section.block.conditional.if.python
          pop: true
        - include: expression-in-a-statement
    - match: \bwhile\b
      scope: keyword.control.loop.while.python
      push:
        - meta_scope: meta.statement.loop.while.python
        - include: line-continuation-or-pop
        - match: ':(?!=)'
          scope: punctuation.section.block.loop.while.python
          pop: true
        - include: expression-in-a-statement
    - match: \b(else)\b(?:\s*(:))?
      scope: meta.statement.conditional.else.python
      captures:
        1: keyword.control.conditional.else.python
        2: punctuation.section.block.conditional.else.python
    - match: \b(try)\b(?:\s*(:))?
      scope: meta.statement.exception.try.python
      captures:
        1: keyword.control.exception.try.python
        2: punctuation.section.block.exception.try.python
    - match: \b(finally)\b(?:\s*(:))?
      scope: meta.statement.exception.finally.python
      captures:
        1: keyword.control.exception.finally.python
        2: punctuation.section.block.exception.finally.python
    - match: \belif\b
      scope: keyword.control.conditional.elseif.python
      push:
        - meta_scope: meta.statement.conditional.elseif.python
        - match: ':(?!=)'
          scope: punctuation.section.block.conditional.elseif.python
          pop: true
        - match: $\n?
          pop: true
        - include: expression-in-a-statement

  with-body:
    - meta_scope: meta.statement.with.python
    - include: line-continuation-or-pop
    - match: \b(as)\b
      scope: keyword.control.flow.with.as.python
      set: with-as
    - match: ':(?!=)'
      scope: punctuation.section.block.with.python
      pop: true
    - match: ','
      scope: punctuation.separator.with-resources.python
    - include: expression-in-a-statement

  with-as:
    - meta_scope: meta.statement.with.python
    - include: line-continuation-or-pop
    - match: ':'
      scope: punctuation.section.block.with.python
      pop: true
    - match: ','
      scope: punctuation.separator.with-resources.python
      set: with-body
    - include: name
    - include: groups
    - include: lists

  expressions-common:
    - include: comments
    - include: constants
    - include: numbers
    - include: yields
    - include: operators
    - include: lambda
    - match: \b(await)\b
      scope: keyword.other.await.python
    - include: inline-if
    - include: strings
    - include: function-calls
    - include: item-access
    - include: lists
    - include: dictionaries-and-sets
    - include: tuples
    - include: groups
    - match: \)
      scope: invalid.illegal.stray.brace.round.python
    - match: \]
      scope: invalid.illegal.stray.brace.square.python
    - match: \}
      scope: invalid.illegal.stray.brace.curly.python
    - include: line-continuation

  # Always include these last and only one at a time!
  expression-as-a-statement:
    - include: expressions-common
    - include: qualified-name

  expression-in-a-statement:
    # Differs from expression-as-a-statement in that:
    # - invalid-name matches will pop the current context
    # - assignment expressions
    - include: expressions-common
    - include: illegal-names-pop
    - include: qualified-name
    - include: assignment-expression

  expression-in-a-group:  # Always include this last!
    # Differs from expression-in-a-statement in that:
    # - accessor matching continues into the next line
    - include: expression-in-a-statement
    - match: '(\.) *(?={{identifier}})'
      captures:
        1: punctuation.accessor.dot.python
      push:
        - include: magic-function-names
        - include: magic-variable-names
        - include: illegal-names
        - include: generic-names
        - match: ''
          pop: true

  after-expression:
    # direct function call
    - match: '\s*(\()'
      captures:
        1: punctuation.section.arguments.begin.python
      push: [function-call-arguments, allow-unpack-operators]
    # item access
    - match: '\s*(\[)'
      captures:
        1: meta.item-access.python punctuation.section.brackets.begin.python
      push:
        - meta_content_scope: meta.item-access.arguments.python
        - match: \]
          scope: meta.item-access.python punctuation.section.brackets.end.python
          pop: true
        - include: illegal-assignment-expression
        - match: ':'
          scope: punctuation.separator.slice.python
        - include: expression-in-a-group
    # indirect function call following attribute access
    - include: function-calls
    # arbitrary attribute access
    - match: '\s*(\.)'
      captures:
        1: punctuation.accessor.dot.python
      push:
        - include: magic-function-names
        - include: magic-variable-names
        - include: illegal-names
        - include: generic-names
        - match: ''
          pop: true
    - match: ''
      pop: true

  comments:
    - match: "#"
      scope: punctuation.definition.comment.python
      push:
        - meta_scope: comment.line.number-sign.python
        - match: \n
          pop: true

  constants:
    - match: \b(None|True|False|Ellipsis|NotImplemented|__debug__)\b
      scope: constant.language.python
    - match: \.{3}(?!\w)
      scope: constant.language.python

  numbers:
    # https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals
    # hexadecimal
    - match: \b(0[xX])(\h*)([lL]) # py2
      scope: meta.number.integer.hexadecimal.python
      captures:
        1: constant.numeric.base.python
        2: constant.numeric.value.python
        3: constant.numeric.suffix.python
    - match: \b(0[xX])((?:_?\h)+)
      scope: meta.number.integer.hexadecimal.python
      captures:
        1: constant.numeric.base.python
        2: constant.numeric.value.python
    # octal
    - match: \b(0[oO]?)((?=[oO]|[0-7])[0-7]*)([lL]) # py2
      scope: meta.number.integer.octal.python
      captures:
        1: constant.numeric.base.python
        2: constant.numeric.value.python
        3: constant.numeric.suffix.python
    - match: \b(0)([0-7]+) # py2
      scope: meta.number.integer.octal.python
      captures:
        1: constant.numeric.base.python
        2: constant.numeric.value.python
    - match: \b(0[oO])((?:_?[0-7])+)
      scope: meta.number.integer.octal.python
      captures:
        1: constant.numeric.base.python
        2: constant.numeric.value.python
    # binary
    - match: \b(0[bB])([01]*)([lL]) # py2
      scope: meta.number.integer.binary.python
      captures:
        1: constant.numeric.base.python
        2: constant.numeric.value.python
        3: constant.numeric.suffix.python
    - match: \b(0[bB])((?:_?[01])*)
      scope: meta.number.integer.binary.python
      captures:
        1: constant.numeric.base.python
        2: constant.numeric.value.python
    # complex
    - match: |-
        (?x)
        (
          # 1.j, 1.1j, 1.1e1j, 1.1e-1j, 1.e1j, 1.e-1 | 1e1j, 1e-1j
          \b{{digits}} (\.)? {{digits}}? {{exponent}}?
          # .1j, .1e1j, .1e-1j
          | (\.) {{digits}} {{exponent}}?
        )
        ([jJ])
      scope: meta.number.imaginary.decimal.python
      captures:
        1: constant.numeric.value.python
        2: punctuation.separator.decimal.python
        3: punctuation.separator.decimal.python
        4: constant.numeric.suffix.python
    # floating point
    - match: |-
        (?x:
          # 1., 1.1, 1.1e1, 1.1e-1, 1.e1, 1.e-1 | 1e1, 1e-1
          \b{{digits}} (?: (\.) {{digits}}? {{exponent}}? | {{exponent}} )
          # .1, .1e1, .1e-1
          | (\.) {{digits}} {{exponent}}?
        )
      scope: meta.number.float.decimal.python constant.numeric.value.python
      captures:
        1: punctuation.separator.decimal.python
        2: punctuation.separator.decimal.python
    # integer
    - match: \b([1-9]\d*|0)([lL])\b # py2
      scope: meta.number.integer.decimal.python
      captures:
        1: constant.numeric.value.python
        2: constant.numeric.suffix.python
    - match: \b([1-9][\d_]*|0)\b
      scope: meta.number.integer.decimal.python constant.numeric.value.python

  modifiers:
    - match: \b(?:(global)|(nonlocal))\b
      captures:
        1: storage.modifier.global.python
        2: storage.modifier.nonlocal.python
      push:
        - include: line-continuation-or-pop
        - match: ','
          scope: punctuation.separator.storage-list.python
        - include: name
        - match: \S+
          scope: invalid.illegal.name.storage.python

  yields:
    - match: \b(yield)(?:\s+(from))?\b
      captures:
        1: keyword.control.flow.yield.python
        2: keyword.control.flow.yield-from.python

  assignment-expression:
    - match: :=
      scope: keyword.operator.assignment.inline.python

  illegal-assignment-expression:
    - match: :=
      scope: invalid.illegal.not-allowed-here.python

  assignments:
    - include: illegal-assignment-expression
    - match: ':'
      scope: punctuation.separator.annotation.variable.python
    - match: \+=|-=|\*=|/=|//=|%=|@=|&=|\|=|\^=|>>=|<<=|\*\*=
      scope: keyword.operator.assignment.augmented.python
    - match: '=(?!=)'
      scope: keyword.operator.assignment.python

  operators:
    - match: <>
      scope: invalid.deprecated.operator.python
    - match: <\=|>\=|\=\=|<|>|\!\=
      scope: keyword.operator.comparison.python
    - match: \+|\-|\*|\*\*|/|//|%|<<|>>|&|\||\^|~
      scope: keyword.operator.arithmetic.python
    - match: \b(and|in|is|not|or)\b
      comment: keyword operators that evaluate to True or False
      scope: keyword.operator.logical.python
    - match: '@'
      scope: keyword.operator.matrix.python
    - match: ','
      scope: punctuation.separator.sequence.python

  allow-unpack-operators:
    # Match unpacking operators, if present
    - include: comments
    - match: \*{3,}
      scope: invalid.illegal.syntax.python
      pop: true
    - match: \*\*
      scope: keyword.operator.unpacking.mapping.python
      pop: true
    - match: \*
      scope: keyword.operator.unpacking.sequence.python
      pop: true
    - match: (?=\S)
      pop: true

  classes:
    - match: '^\s*(class)\b'
      captures:
        1: keyword.declaration.class.python
      push:
        - meta_scope: meta.class.python
        - include: line-continuation-or-pop
        - match: ':'
          scope: punctuation.section.class.begin.python
          pop: true
        - match: "(?={{identifier}})"
          push:
            - meta_content_scope: entity.name.class.python
            - include: entity-name-class
            - match: ''
              pop: true
        - match: \(
          scope: punctuation.section.inheritance.begin.python
          set:
            - meta_scope: meta.class.inheritance.python
            - match: \)
              scope: punctuation.section.inheritance.end.python
              set:
                - include: line-continuation-or-pop
                - match: ':'
                  scope: meta.class.python punctuation.section.class.begin.python
                  pop: true
                - match: (?=\S)
                  pop: true
            - match: ':'
              scope: invalid.illegal.no-closing-parens.python
              pop: true
            - match: ','
              scope: punctuation.separator.inheritance.python
            - include: illegal-names-pop
            - match: ({{identifier}}) *(=)
              captures:
                1: variable.parameter.class-inheritance.python
                2: keyword.operator.assignment.python
            - match: (?={{path}})
              push:
                - meta_scope: entity.other.inherited-class.python
                - match: '{{identifier}}(?: *(\.) *)?'
                  captures:
                    1: punctuation.accessor.dot.python
                - match: ''
                  pop: true
            - include: expression-in-a-group

  functions:
    - match: '^\s*(?:(async)\s+)?(def)\b'
      captures:
        1: keyword.declaration.async.python
        2: keyword.declaration.function.python
      push:
        - meta_scope: meta.function.python
        - include: line-continuation-or-pop
        - match: ':'
          scope: punctuation.section.function.begin.python
          pop: true
        - match: "(?={{identifier}})"
          push:
            - meta_content_scope: entity.name.function.python
            - include: entity-name-function
            - match: ''
              pop: true
        - match: '(?=\()'
          set:
            - match: \(
              scope: meta.function.parameters.python punctuation.section.parameters.begin.python
              set: [function-parameters, allow-unpack-operators]

  function-parameters:
    - meta_content_scope: meta.function.parameters.python
    - match: \)
      scope: punctuation.section.parameters.end.python
      set: function-after-parameters
    - include: comments
    - match: ','
      scope: punctuation.separator.parameters.python
      push: allow-unpack-operators
    - match: /
      scope: storage.modifier.positional-args-only.python
      push:
        - match: (?=[,)])
          pop: true
        - match: \S
          scope: invalid.illegal.expected-comma.python
    - match: '(?==)'
      set:
        - match: '='
          scope: keyword.operator.assignment.python
          set:
            - meta_scope: meta.function.parameters.default-value.python
            - match: '(?=[,)])'
              set: [function-parameters, allow-unpack-operators]
            - include: illegal-assignment-expression
            - include: expression-in-a-group
    - match: '(?=:)'
      set:
        - match: ':'
          scope: punctuation.separator.annotation.parameter.python
          set:
            - meta_scope: meta.function.parameters.annotation.python
            - match: '(?=[,)=])'
              set: function-parameters
            - include: illegal-assignment-expression
            - include: expression-in-a-group
    - include: function-parameters-tuple
    - include: illegal-names
    - match: '{{identifier}}'
      scope: variable.parameter.python
    - include: line-continuation

  function-parameters-tuple:
    # python 2 style tuple arguments
    # removed from python 3 since PEP-3113
    - match: \(
      scope: punctuation.section.group.begin.python
      push:
        - meta_scope: meta.group.python
        - match: \)
          scope: punctuation.section.group.end.python
          set: after-expression
        - include: comments
        - match: ','
          scope: punctuation.separator.parameters.python
          push: allow-unpack-operators
        # default values should follow the argument
        - match: '='
          push:
            - meta_scope: invalid.illegal.default-value.python
            - match: '(?=[,)=])'
              pop: true
        # python 2 does not support type annotations
        - match: '(?=:)'
          push:
            - meta_scope: invalid.illegal.annotation.python
            - match: '(?=[,)=])'
              pop: true
        - include: illegal-names
        - match: '{{identifier}}'
          scope: variable.parameter.python
        - include: line-continuation

  function-after-parameters:
    - meta_content_scope: meta.function.python
    - match: (?=->)
      set: [function-return-type, function-return-type-separator]
    - include: function-terminator

  function-return-type:
    - meta_content_scope: meta.function.annotation.return.python
    - include: illegal-assignment-expression
    - include: function-terminator
    - include: expression-in-a-statement

  function-return-type-separator:
    - match: ->
      scope: punctuation.separator.annotation.return.python
      pop: true

  function-terminator:
    - match: ':'
      scope: meta.function.python punctuation.section.function.begin.python
      pop: true
    - include: line-continuation-or-pop

  decorators:
    - match: ^\s*(?=@)
      push:
        # Due to line continuations, we don't know whether this is a "function call" yet
        - meta_content_scope: meta.annotation.python
        - match: '@'
          scope: punctuation.definition.annotation.python
        - match: $
          pop: true
        - include: line-continuation-or-pop
        - match: (?=\.?\s*{{path}}\s*\() # now we do
          set: [after-expression, decorator-function-call-wrapper, qualified-name-until-leaf]
        - match: (?=\.?\s*{{path}})
          push: [decorator-wrapper, qualified-name-until-leaf]
        - match: \S
          scope: invalid.illegal.character.python
          pop: true

  decorator-wrapper:
    - match: (\.)\s*
      captures:
        1: punctuation.accessor.dot.python
      set:
        - meta_scope: meta.qualified-name.python
        - meta_content_scope: variable.annotation.python
        - include: dotted-name-specials
        - include: generic-names
        - match: ''
          pop: true
    - match: ''
      set:
        - meta_scope: meta.qualified-name.python variable.annotation.python
        - include: name-specials
        - include: generic-names
        - match: ''
          pop: true

  decorator-function-call-wrapper:
    - meta_scope: meta.annotation.function.python
    - match: \)
      scope: punctuation.section.arguments.end.python
      pop: true
    - match: \(
      scope: meta.annotation.function.python punctuation.section.arguments.begin.python
      push: [decorator-function-call-arguments, allow-unpack-operators]
    - match: (\.)\s*
      captures:
        1: punctuation.accessor.dot.python
      push:
        - meta_scope: meta.qualified-name.python
        - meta_content_scope: variable.annotation.function.python
        - include: dotted-name-specials
        - include: generic-names
        - match: ''
          pop: true
    - match: ''
      push:
        - meta_scope: meta.qualified-name.python variable.annotation.function.python
        - include: name-specials
        - include: generic-names
        - match: ''
          pop: true

  decorator-function-call-arguments:
    - clear_scopes: 1
    - meta_content_scope: meta.annotation.arguments.python
    - match: (?=\))
      pop: true
    - include: arguments

  item-access:
    - match: '(?={{path}}\s*\[)'
      push:
        - match: \]
          scope: meta.item-access.python punctuation.section.brackets.end.python
          set: after-expression
        - match: '(?={{path}}\s*\[)'
          push:
            - meta_content_scope: meta.item-access.python
            - match: '(?=\s*\[)'
              pop: true
            - include: qualified-name
        - match: \[
          scope: meta.item-access.python punctuation.section.brackets.begin.python
          push:
            - meta_content_scope: meta.item-access.arguments.python
            - match: '(?=\])'
              pop: true
            - match: ':'
              scope: punctuation.separator.slice.python
            - include: expression-in-a-group

  function-calls:
    - match: '(?=(\.\s*)?{{path}}\s*\()'
      push: [function-call-wrapper, qualified-name-until-leaf]

  function-call-wrapper:
    - meta_scope: meta.function-call.python
    - match: (?=\()  # need to remove meta.function-call.python from opening parens
      set:
        - match: \(
          scope: punctuation.section.arguments.begin.python
          set: [after-expression, function-call-arguments, allow-unpack-operators]
    - match: (\.)\s*(?={{identifier}})
      captures:
        1: punctuation.accessor.dot.python
      push:
        - meta_scope: meta.qualified-name.python
        - meta_content_scope: variable.function.python
        - include: dotted-name-specials
        - include: generic-names
        - match: ''
          pop: true
    - match: (?={{identifier}})
      push:
        - meta_scope: meta.qualified-name.python variable.function.python
        - include: name-specials
        - include: generic-names
        - match: ''
          pop: true

  function-call-arguments:
    - meta_scope: meta.function-call.arguments.python
    - match: \)
      scope: punctuation.section.arguments.end.python
      pop: true
    - include: arguments

  arguments:
    - include: keyword-arguments
    - match: ','
      scope: punctuation.separator.arguments.python
      push: allow-unpack-operators
    - include: inline-for
    - include: expression-in-a-group

  keyword-arguments:
    - match: '(?={{identifier}}\s*=(?!=))'
      push:
        - include: line-continuation-or-pop
        - match: '='
          scope: keyword.operator.assignment.python
          set:
            - include: illegal-assignment-expression
            - match: (?=[,):])
              pop: true
            - include: expression-in-a-group
        - include: illegal-names
        - match: '{{identifier}}'
          scope: variable.parameter.python

  lambda:
    - match: \b(lambda)(?=\s|:|$)
      scope:
        meta.function.inline.python
        storage.type.function.inline.python
        keyword.declaration.function.inline.python
      push: [lambda-parameters, allow-unpack-operators]

  lambda-parameters:
    - meta_content_scope: meta.function.inline.parameters.python
    - include: line-continuation-or-pop
    - match: '\:'
      scope: punctuation.section.function.begin.python
      set:
        # clear meta_scope
        - match: ''
          set:
            - meta_scope: meta.function.inline.body.python
            - include: illegal-assignment-expression
            # We don't know whether we are within a grouped
            # or line-statement context at this point.
            # If we're in a group, the underlying context will take over
            # at the end of the line.
            - match: (?=[,:)}\]])|$
              pop: true
            - include: expression-in-a-statement
    - match: ','
      scope: punctuation.separator.parameters.python
      push: allow-unpack-operators
    - include: keyword-arguments
    - include: function-parameters-tuple
    - include: illegal-names
    - match: '{{identifier}}'
      scope: variable.parameter.python
    - match: '\S'
      scope: invalid.illegal.expected-parameter.python

  groups:
    - match: \(
      scope: punctuation.section.group.begin.python
      push:
        - meta_scope: meta.group.python
        - match: \)
          scope: punctuation.section.group.end.python
          set: after-expression
        - match: ','
          scope: punctuation.separator.tuple.python
        - include: inline-for
        - include: expression-in-a-group

  tuples:
    # We don't know for certain, whether a parenthesized expression is a tuple,
    # so try looking ahead.
    - match: (\()\s*(\))
      scope: meta.sequence.tuple.empty.python
      captures:
        1: punctuation.section.sequence.begin.python
        2: punctuation.section.sequence.end.python
      push: after-expression
    - match: \((?={{simple_expression}},|\s*\*{{path}})
      scope: punctuation.section.sequence.begin.python
      push: inside-tuple
    # TODO generator
    # - match: \((?:{{simple_expression}}for)

  inside-tuple:
    - meta_scope: meta.sequence.tuple.python
    - match: \)
      scope: punctuation.section.sequence.end.python
      set: after-expression
    - match: ','
      scope: punctuation.separator.sequence.python
      push: allow-unpack-operators
    - include: inline-for
    - include: expression-in-a-group

  lists:
    - match: (\[)\s*(\])
      scope: meta.sequence.list.empty.python
      captures:
        1: punctuation.section.sequence.begin.python
        2: punctuation.section.sequence.end.python
      push: after-expression
    - match: \[
      scope: punctuation.section.sequence.begin.python
      push: [inside-list, allow-unpack-operators]

  inside-list:
    - meta_scope: meta.sequence.list.python
    - match: \]
      scope: punctuation.section.sequence.end.python
      set: after-expression
    - match: ','
      scope: punctuation.separator.sequence.python
      push: allow-unpack-operators
    - include: inline-for
    - include: expression-in-a-group

  dictionaries-and-sets:
    # Dictionaries and set literals use the same punctuation,
    # so we try looking ahead to determine whether we have a dict or a set.
    - match: '(\{)\s*(\})'
      scope: meta.mapping.empty.python
      captures:
        1: punctuation.section.mapping.begin.python
        2: punctuation.section.mapping.end.python
      push: after-expression
    - match: \{(?={{simple_expression}}:|\s*\*\*)
      scope: punctuation.section.mapping.begin.python
      push: inside-dictionary
    - match: \{(?={{simple_expression}}[,}]|\s*\*)
      scope: punctuation.section.set.begin.python
      push: inside-set
    # If the expression is "more complex" or on the next line,
    # fall back to default and determine later.
    - match: \{
      scope: punctuation.section.mapping-or-set.begin.python
      push:
        - meta_scope: meta.mapping-or-set.python
        - match: \}
          scope: punctuation.section.mapping-or-set.end.python
          set: after-expression
        - match: (?={{simple_expression}}:|\s*\*\*)
          set: inside-dictionary
        - match: (?={{simple_expression}}[,}]|\s*\*)
          set: inside-set
        - match: ','
          scope: punctuation.separator.set.python
          set: inside-set
        - include: illegal-assignment-expression
        - match: ':'
          scope: punctuation.separator.mapping.key-value.python
          set: inside-directory-value
        - include: inline-for
        - include: expression-in-a-group

  inside-dictionary:
    - meta_scope: meta.mapping.python
    - match: \}
      scope: punctuation.section.mapping.end.python
      set: after-expression
    - include: illegal-assignment-expression
    - match: ':'
      scope: punctuation.separator.mapping.key-value.python
      set: inside-directory-value
    - match: \*\*
      scope: keyword.operator.unpacking.mapping.python
      push:
        - match: (?=\})
          pop: true
        - match: ','
          scope: punctuation.separator.mapping.python
          pop: true
        - include: expression-in-a-group
    - include: comments
    - match: (?=\S)
      push:
        - clear_scopes: 1
        - meta_scope: meta.mapping.key.python
        - match: \s*(?=\}|,|:)
          pop: true
        - include: expression-in-a-group

  inside-directory-value:
    - meta_content_scope: meta.mapping.python
    - match: \}
      scope: punctuation.section.mapping.end.python
      set: after-expression
    - match: (?=,)
      set:
        # clear meta scope from this match, because 'inside-directory' has it in meta_scope
        - match: ','
          scope: punctuation.separator.mapping.python
          set: inside-dictionary
    - match: (?=(?:async|for)\b)
      push:
        - match: (?=\})
          pop: true
        - match: ','
          scope: invalid.illegal.unexpected-comma.python
        - include: inline-for
        - include: expression-in-a-group
    - include: comments
    - match: (?=\S)
      push:
        - clear_scopes: 1
        - meta_content_scope: meta.mapping.value.python
        - match: (?=\s*(\}|,|(?:async|for)\b))
          pop: true
        - include: expression-in-a-group

  inside-set:
    - meta_scope: meta.set.python
    - match: \}
      scope: punctuation.section.set.end.python
      set: after-expression
    - include: illegal-assignment-expression
    - match: ':'
      scope: invalid.illegal.colon-inside-set.python
    - match: ','
      scope: punctuation.separator.set.python
    - match: \*
      scope: keyword.operator.unpacking.sequence.python
      push:
        - match: (?=\})
          pop: true
        - match: ','
          scope: punctuation.separator.set.python
          pop: true
        - include: expression-in-a-group
    - include: inline-for
    - include: expression-in-a-group

  builtin-exceptions:
    - match: |-
        (?x)\b(
          (?:
            Arithmetic|Assertion|Attribute|BlockingIO|BrokenPipe|Buffer|ChildProcess|
            Connection(?:Aborted|Refused|Reset)?|EOF|Environment|FileExists|
            FileNotFound|FloatingPoint|Interrupted|IO|IsADirectoryError|
            Import|Indentation|Index|Key|Lookup|Memory|Name|NotADirectory|
            NotImplemented|OS|Overflow|Permission|ProcessLookup|Reference|
            Runtime|Standard|Syntax|System|Tab|Timeout|Type|UnboundLocal|
            Unicode(?:Encode|Decode|Translate)?|Value|VMS|Windows|ZeroDivision
          )Error|
          (?:(?:Pending)?Deprecation|Resource|Runtime|Syntax|User|Future|Import|Unicode|Bytes)?Warning|
          (?:Base)?Exception|
          SystemExit|StopIteration|NotImplemented|KeyboardInterrupt|GeneratorExit
        )\b
      scope: support.type.exception.python

  builtin-functions:
    - match: |-
        (?x)\b(?:
          __import__|all|abs|any|ascii|bin|callable|chr|classmethod|
          compile|delattr|dir|divmod|enumerate|eval|filter|format|getattr|
          globals|hasattr|hash|help|hex|id|input|isinstance|issubclass|iter|
          len|locals|map|max|min|next|oct|open|ord|pow|property|range|
          repr|reversed|round|setattr|sorted|staticmethod|
          sum|super|type|vars|zip
          # Python 2 functions
          |apply|cmp|coerce|execfile|intern|raw_input|reduce|reload|unichr|xrange
          # Python 3 functions
          |breakpoint|exec
        )\b
      scope: support.function.builtin.python

  builtin-types:
    - match: |-
        (?x)\b(?:
          bool|bytearray|bytes|complex|dict|float|frozenset|int|
          list|memoryview|object|set|slice|str|tuple
          # Python 2 types
          |basestring|long|unicode
          # Python 2 types prone to conflicts
          # |buffer|file
        )\b
      scope: support.type.python

  name:
    - match: '(?={{identifier}})'
      push:
        - include: name-specials
        - match: '{{identifier_constant}}'
          scope: variable.other.constant.python
        - include: generic-names
        - match: ''
          pop: true

  dotted-name:
    - match: '\s*(\.)\s*(?={{identifier}})'
      captures:
        1: punctuation.accessor.dot.python
      push:
        - include: dotted-name-specials
        - match: '{{identifier_constant}}'
          scope: variable.other.constant.python
        - include: generic-names
        - match: ''
          pop: true

  qualified-name:
    - match: '(?={{path}})'
      push:
        - meta_scope: meta.qualified-name.python
        - include: name
        - include: dotted-name
        - match: ''
          pop: true
    - match: \.
      scope: punctuation.accessor.dot.python

  qualified-name-until-leaf:
    # Push this together with another context to match a qualified name
    # until the last non-special identifier (if any).
    # This allows the leaf to be scoped individually.
    - meta_scope: meta.qualified-name.python
    # If a line continuation follows, this may or may not be the last leaf (most likley not though)
    - match: (?={{identifier}}\s*(\.|\\))
      push:
        - include: name-specials
        - include: generic-names
        - match: ''
          pop: true
    - match: (\.)\s*(?={{identifier}}\s*(\.|\\))
      captures:
        1: punctuation.accessor.dot.python
      push:
        - include: dotted-name-specials
        - include: generic-names
        - match: ''
          pop: true
    - match: \.(?!\s*{{identifier}})  # don't match last dot
      scope: punctuation.accessor.dot.python
    - match: (?=\S|$)
      pop: true

  name-specials:
    - include: builtin-functions
    - include: builtin-types
    - include: builtin-exceptions
    - include: illegal-names
    - include: magic-function-names
    - include: magic-variable-names
    - include: language-variables

  dotted-name-specials:
    - include: magic-function-names
    - include: magic-variable-names
    - include: illegal-names

  entity-name-class:
    - include: illegal-names
    - include: generic-names

  entity-name-function:
    - include: magic-function-names
    - include: illegal-names
    - include: generic-names

  generic-names:
    - match: '{{identifier}}'
      scope: meta.generic-name.python

  illegal-names:
    - match: \b{{illegal_names}}\b
      scope: invalid.illegal.name.python

  illegal-names-pop:
    - match: \b{{illegal_names}}\b
      scope: invalid.illegal.name.python
      pop: true

  language-variables:
    - match: \b(self|cls)\b
      scope: variable.language.python
    - match: _(?!{{identifier_continue}})
      scope: variable.language.python

  line-continuation:
    - match: (\\)(.*)$\n?
      captures:
        1: punctuation.separator.continuation.line.python
        2: invalid.illegal.unexpected-text.python
    # make sure to resume parsing at next line
      push:
        # This prevents strings after a continuation from being a docstring
        - include: strings
        - match: (?=\S|^\s*$|\n)  # '\n' for when we matched a string earlier
          pop: true

  line-continuation-or-pop:
    - include: line-continuation
    - match: (?=\s*($|;|#))
      pop: true

  magic-function-names:
    # https://docs.python.org/2/reference/datamodel.html
    # https://docs.python.org/3/reference/datamodel.html
    - match: |-
        (?x)\b__(?:
          # unary operators
          invert|neg|pos|abs|
          # binary operators
          add|and|div|divmod|floordiv|lshift|mod|mul|or|pow|rshift|sub|truediv|xor|
          contains|
          # right-hand binary operators
          radd|rand|rdiv|rdivmod|rfloordiv|rlshift|rmod|rmul|ror|rpow|rrshift|rsub|rtruediv|rxor|
          # in-place operator assignments
          iadd|iand|idiv|ifloordiv|ilshift|imod|imul|ior|ipow|irshift|isub|itruediv|ixor|
          # comparisons
          eq|ge|gt|le|lt|ne|
          cmp|rcmp| # py2
          # primary coercion
          bool|str|
          nonzero|unicode| # py2
          # number coercion (converts something to a number)
          bytes|complex|float|index|int|round|
          long| # py2
          # other "coercion"
          format|len|length_hint|hash|repr|reversed|
          coerce|hex|oct| # py2
          fspath|
          # iterator (and 'await')
          iter|next|
          aiter|anext|
          await|
          # attribute and item access
          delattr|delitem|delslice|
          getattr|getattribute|getitem|getslice|
          setattr|setitem|setslice|
          dir|missing|
          # context manager
          enter|exit|
          aenter|aexit|
          # other class magic
          call|del|init|new|init_subclass|
          instancecheck|subclasscheck|
          # pickling
          getnewargs|getnewargs_ex|getstate|setstate|reduce|reduce_ex|
          # descriptors
          delete|get|set|set_name|
          # class-specific
          subclasses|
          # dataclasses (PEP 557)
          post_init|
          # for typing core support (PEP 560)
          class_getitem|mro_entries
        )__\b
      comment: these methods have magic interpretation by python and are generally called indirectly through syntactic constructs
      scope: support.function.magic.python

  magic-variable-names:
    # magic variables which a class/module/object may have.
    # https://docs.python.org/3/library/inspect.html#types-and-members
    # https://docs.python.org/3/reference/datamodel.html#object.__slots__
    # https://docs.python.org/3/reference/datamodel.html#preparing-the-class-namespace
    - match: |-
        (?x)\b__(?:
          # generic object
          class|dict|doc|module|name|
          # module-specific / global
          all|file|package|
          # functions & methods
          annotations|closure|code|defaults|func|globals|kwdefaults|self|qualname|
          # classes (attributes)
          bases|prepare|slots|metaclass|mro|
          # Python 2
          members|methods
        )__\b
      scope: support.variable.magic.python

  docstrings:
    - match: ^\s*(?=(?i)(ur|ru|u|r)?("""|'''))
      push:
      - match: (?i)(u)?("""|''')
        captures:
          1: storage.type.string.python
          2: punctuation.definition.comment.begin.python
        set:
          - meta_scope: comment.block.documentation.python
          - include: escaped-unicode-char
          - include: escaped-char
          - match: '\2'
            scope: punctuation.definition.comment.end.python
            pop: true
      - match: (?i)(u?ru?)("""|''')
        captures:
          1: storage.type.string.python
          2: punctuation.definition.comment.begin.python
        set:
          - meta_scope: comment.block.documentation.python
          - match: '\2'
            scope: punctuation.definition.comment.end.python
            pop: true

  escaped-char:
    - match: '(\\x\h{2})|(\\[0-7]{1,3})|(\\[\\"''abfnrtv])'
      captures:
        1: constant.character.escape.hex.python
        2: constant.character.escape.octal.python
        3: constant.character.escape.python
    - match: \\.  # deprecated in 3.6 and will eventually be a syntax error
      scope: invalid.deprecated.character.escape.python

  escaped-unicode-char:
    - match: '(\\U\h{8})|(\\u\h{4})|(\\N\{[-a-zA-Z ]+\})'
      captures:
        1: constant.character.escape.unicode.16-bit-hex.python
        2: constant.character.escape.unicode.32-bit-hex.python
        3: constant.character.escape.unicode.name.python

  escaped-fstring-escape:
    # special-case the '\{{' sequence because it has higher priority than the deprecated '\{'
    - match: (\\)(\{\{|\}\})
      scope: constant.character.escape.backslash.regexp
      captures:
        1: invalid.deprecated.character.escape.python
        2: constant.character.escape.python

  line-continuation-inside-string:
    - match: (\\)$\n?
      captures:
        1: punctuation.separator.continuation.line.python
    - match: \n
      scope: invalid.illegal.unclosed-string.python
      set: after-expression

  line-continuation-inside-block-string:
    - match: \\$
      scope: punctuation.separator.continuation.line.python

  constant-placeholder:
    - match: |- # printf style
        (?x)
        %
          ( \( ({{identifier}}) \) )? # mapping key
          \#?            # alternate form
          0?             # pad with zeros
          \-?            # left-adjust
          \ ?            # implicit sign
          [+-]?          # sign
          (\d*|\*)       # width
          (\. (\d*|\*))? # precision
          [hlL]?         # length modifier (but ignored)
          [acdeEfFgGiorsuxX%]
      scope: constant.other.placeholder.python
      captures:
        2: variable.other.placeholder.python
    - match: '{{strftime_spec}}'
      scope: constant.other.placeholder.python
    - match: '\{\{|\}\}'
      scope: constant.character.escape.python
    - include: formatting-syntax

  formatting-syntax:
    # https://docs.python.org/3.6/library/string.html#formatstrings
    # Technically allows almost every character for the key,
    # but those are rarely used if ever.
    - match: |- # simple form
        (?x)
        (\{)
          (?: [\w.\[\]]+)?           # field_name
          (   ! [ars])?              # conversion
          (?: (:) ({{format_spec}}|  # format_spec OR
                   [^}%]*%.[^}]*)    # any format-like string
          )?
        (\})
      scope: constant.other.placeholder.python
      captures:
        1: punctuation.definition.placeholder.begin.python
        2: storage.modifier.conversion.python
        3: punctuation.separator.format-spec.python
        4: meta.format-spec.python constant.other.format-spec.python
        5: punctuation.definition.placeholder.end.python
    - match: (?=\{[^{}"']+\{[^"']*\})  # complex (nested) form
      branch_point: formatting-syntax-branch
      branch:
        - formatting-syntax-complex
        - formatting-syntax-fallback

  formatting-syntax-fallback:
    - match: \{
      scope: meta.debug.formatting-syntax-fallback.python
      pop: true

  formatting-syntax-complex:
    - match: \{
      scope: punctuation.definition.placeholder.begin.python
      set:
        - meta_scope: constant.other.placeholder.python
        - match: \}
          scope: punctuation.definition.placeholder.end.python
          pop: true
        # TODO could match numeric indices or everything else as a key
        # and also [] indexing
        - match: '![ars]'
          scope: storage.modifier.conversion.python
        - match: ':'
          scope: punctuation.separator.format-spec.python
          push:
            - meta_content_scope: meta.format-spec.python constant.other.format-spec.python
            - match: (?=\})
              pop: true
            - match: (?=\{)
              push: formatting-syntax-complex
        - match: '[{"''\n]'
          fail: formatting-syntax-branch

  f-string-content:
    # https://www.python.org/dev/peps/pep-0498/
    # https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings
    - match: \{\{|\}\}
      scope: constant.character.escape.python
    - match: \{\s*\}
      scope: invalid.illegal.empty-expression.python
    - match: (?=\{)
      push: f-string-replacement
    - match: \}
      scope: invalid.illegal.stray-brace.python

  f-string-content-with-regex:
    # Same as f-string-content, but will reset the entire scope stack
    # and has an additional match.
    - match: \\(\{\{|\}\})
      scope: constant.character.escape.backslash.regexp
      captures:
        1: constant.character.escape.python
    - match: \{\{|\}\}
      scope: constant.character.escape.python
    - match: \{\s*\}
      scope: invalid.illegal.empty-expression.python
    - match: (?=\{)
      push: f-string-replacement-reset
    - match: \}
      scope: invalid.illegal.stray-brace.python

  f-string-replacement:
    - clear_scopes: 1
    - match: \}
      scope: meta.interpolation.python punctuation.section.interpolation.end.python
      pop: true
    - match: \{
      scope: punctuation.section.interpolation.begin.python
      push:
        - meta_scope: meta.interpolation.python
        - match: (?=\})
          pop: true
        - match: '![ars]'
          scope: storage.modifier.conversion.python
        - match: =
          scope: storage.modifier.debug.python
        - match: ':'
          push:
            - meta_scope: meta.format-spec.python constant.other.format-spec.python
            # Because replacements can also be used *within* the format-spec,
            # basically any character is valid and matching {{format_spec}} is useless.
            # - match: '{{format_spec}}'
            - match: (?=\})
              pop: true
            - include: f-string-content
        - match: ''
          push:
            - meta_content_scope: source.python.embedded
            - match: (?==?(![^=]|:|\}))
              pop: true
            - match: \\
              scope: invalid.illegal.backslash-in-fstring.python
            - include: inline-for
            - include: expression-in-a-group

  f-string-replacement-reset:
    # Same as f-string-replacement, but with clear_scopes: true
    - clear_scopes: true
    - meta_scope: source.python meta.string.interpolated.python
    - match: \}
      scope: meta.interpolation.python punctuation.section.interpolation.end.python
      pop: true
    - match: \{
      scope: punctuation.section.interpolation.begin.python
      push:
        - meta_scope: meta.interpolation.python
        - match: (?=\})
          pop: true
        - match: '![ars]'
          scope: storage.modifier.conversion.python
        - match: ':'
          push:
            - meta_scope: meta.format-spec.python constant.other.format-spec.python
            - match: (?=\})
              pop: true
            - include: f-string-content
        - match: ''
          push:
            - meta_content_scope: source.python.embedded
            - match: (?=![^=]|:|\})
              pop: true
            - match: \\
              scope: invalid.illegal.backslash-in-fstring.python
            - include: inline-for
            - include: expression-in-a-group

  string-quoted-double-block:
    # Triple-quoted capital R raw string, unicode or not, no syntax embedding
    - match: '([uU]?R)(""")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.block.python
        - match: '"""'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: escaped-unicode-char
    # Triple-quoted capital R raw string, bytes, no syntax embedding
    - match: '([bB]R|R[bB])(""")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.block.python
        - match: '"""'
          scope: punctuation.definition.string.end.python
          set: after-expression
    # Triple-quoted raw string, unicode or not, will detect SQL, otherwise regex
    - match: '([uU]?r)(""")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.block.python
        - match: '(?={{sql_indicator}})'
          set:
            - meta_scope: meta.string.python string.quoted.double.block.python
            - match: '"""'
              scope: punctuation.definition.string.end.python
              set: after-expression
            - match: ''
              push: scope:source.sql
              with_prototype:
                - match: '(?=""")'
                  pop: true
                - include: escaped-unicode-char
                - include: constant-placeholder
        - match: '(?=\S)'
          set:
            - meta_scope: meta.string.python string.quoted.double.block.python
            - match: '"""'
              scope: punctuation.definition.string.end.python
              set: after-expression
            - match: ''
              push: scope:source.regexp.python
              with_prototype:
                - match: '(?=""")'
                  pop: true
                - include: escaped-unicode-char
    # Triple-quoted raw string, bytes, will use regex
    - match: '([bB]r|r[bB])(""")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.block.python
        - match: '"""'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - match: ''
          embed: scope:source.regexp.python
          escape: (?=""")
    # Triple-quoted raw f-string
    - match: ([fF]R|R[fF])(""")
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.double.block.python
        - match: '"""'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: f-string-content
    # Triple-quoted raw f-string, treated as regex
    - match: ([fF]r|r[fF])(""")
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.double.block.python
        - match: '"""'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - match: ''
          push: scope:source.regexp.python
          with_prototype:
            - match: '(?=""")'
              pop: true
            - include: f-string-content-with-regex
    # Triple-quoted f-string
    - match: ([fF])(""")
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.double.block.python
        - match: '"""'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-block-string
        - include: escaped-fstring-escape
        - include: escaped-unicode-char
        - include: escaped-char
        - include: f-string-content
    # Triple-quoted string, unicode or not, will detect SQL
    - match: '([uU]?)(""")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.block.python
        - match: '(?={{sql_indicator}})'
          set:
            - meta_scope: meta.string.python string.quoted.double.block.python
            - match: '"""'
              scope: punctuation.definition.string.end.python
              set: after-expression
            - match: ''
              push: scope:source.sql
              with_prototype:
                - match: '(?=""")'
                  pop: true
                - include: line-continuation-inside-block-string
                - include: escaped-unicode-char
                - include: escaped-char
                - include: constant-placeholder
        - match: '(?=\S)'
          set:
            - meta_scope: meta.string.python string.quoted.double.block.python
            - match: '"""'
              scope: punctuation.definition.string.end.python
              set: after-expression
            - include: line-continuation-inside-block-string
            - include: escaped-unicode-char
            - include: escaped-char
            - include: constant-placeholder
    # Triple-quoted string, bytes, no syntax embedding
    - match: '([bB])(""")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.block.python
        - match: '"""'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-block-string
        - include: escaped-char
        - include: constant-placeholder

  string-quoted-double:
    # Single-line capital R raw string, unicode or not, no syntax embedding
    - match: '([uU]?R)(")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
    # Single-line capital R raw string, bytes, no syntax embedding
    - match: '([bB]R|R[bB])(")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
    # Single-line raw string, unicode or not, starting with a SQL keyword
    - match: '([uU]?r)(")(?={{sql_indicator}})'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.sql
          with_prototype:
            - match: '(?="|\n)'
              pop: true
            - include: constant-placeholder
            - include: line-continuation-inside-string
    # Single-line raw string, unicode or not, treated as regex
    - match: '([uU]?r)(")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.regexp.python
          with_prototype:
            - match: '(?="|\n)'
              pop: true
            - include: line-continuation-inside-string
    # Single-line raw string, bytes, treated as regex
    - match: '([bB]r|r[bB])(")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          embed: scope:source.regexp.python
          escape: (?="|\n)
    # Single-line raw f-string
    - match: (R[fF]|[fF]R)(")
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - include: f-string-content
    # Single-line raw f-string, treated as regex
    - match: (r[fF]|[fF]r)(")
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.regexp.python
          with_prototype:
            - match: '(?="|\n)'
              pop: true
            - include: line-continuation-inside-string
            - include: f-string-content-with-regex
    # Single-line f-string
    - match: ([fF])(")
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: escaped-fstring-escape
        - include: escaped-unicode-char
        - include: escaped-char
        - include: line-continuation-inside-string
        - include: f-string-content
    # Single-line string, unicode or not, starting with a SQL keyword
    - match: '([uU]?)(")(?={{sql_indicator}})'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.sql
          with_prototype:
            - match: '(?="|\n)'
              pop: true
            - include: escaped-unicode-char
            - include: escaped-char
            - include: line-continuation-inside-string
            - include: constant-placeholder
    # Single-line string, unicode or not
    - match: '([uU]?)(")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: escaped-unicode-char
        - include: escaped-char
        - include: line-continuation-inside-string
        - include: constant-placeholder
    # Single-line string, bytes
    - match: '([bB])(")'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.double.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.double.python
        - match: '"'
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: escaped-char
        - include: line-continuation-inside-string
        - include: constant-placeholder

  string-quoted-single-block:
    # Triple-quoted capital R raw string, unicode or not, no syntax embedding
    - match: ([uU]?R)(''')
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.block.python
        - match: "'''"
          scope: punctuation.definition.string.end.python
          set: after-expression
    # Triple-quoted capital R raw string, bytes, no syntax embedding
    - match: ([bB]R|R[bB])(''')
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.block.python
        - match: "'''"
          scope: punctuation.definition.string.end.python
          set: after-expression
    # Triple-quoted raw string, unicode or not, will detect SQL, otherwise regex
    - match: ([uU]?r)(''')
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.block.python
        - match: '(?={{sql_indicator}})'
          set:
            - meta_scope: meta.string.python string.quoted.single.block.python
            - match: "'''"
              scope: punctuation.definition.string.end.python
              set: after-expression
            - match: ''
              push: scope:source.sql
              with_prototype:
                - match: (?=''')
                  pop: true
                - include: escaped-unicode-char
                - include: escaped-char
                - include: constant-placeholder
        - match: '(?=\S)'
          set:
            - meta_scope: meta.string.python string.quoted.single.block.python
            - match: "'''"
              scope: punctuation.definition.string.end.python
              set: after-expression
            - match: ''
              push: scope:source.regexp.python
              with_prototype:
                - match: (?=''')
                  pop: true
                - include: escaped-unicode-char
    # Triple-quoted raw string, bytes, will use regex
    - match: ([bB]r|r[bB])(''')
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.block.python
        - match: "'''"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - match: ''
          embed: scope:source.regexp.python
          escape: (?=''')
    # Triple-quoted raw f-string
    - match: ([fF]R|R[fF])(''')
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.single.block.python
        - match: "'''"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: f-string-content
    # Triple-quoted raw f-string, treated as regex
    - match: ([fF]r|r[fF])(''')
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.single.block.python
        - match: "'''"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - match: ''
          push: scope:source.regexp.python
          with_prototype:
            - match: (?=''')
              pop: true
            - include: f-string-content-with-regex
    # Triple-quoted f-string
    - match: ([fF])(''')
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.single.block.python
        - match: "'''"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-block-string
        - include: escaped-fstring-escape
        - include: escaped-unicode-char
        - include: escaped-char
        - include: f-string-content
    # Triple-quoted string, unicode or not, will detect SQL
    - match: ([uU]?)(''')
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.block.python
        - match: '(?={{sql_indicator}})'
          set:
            - meta_scope: meta.string.python string.quoted.single.block.python
            - match: "'''"
              scope: punctuation.definition.string.end.python
              set: after-expression
            - match: ''
              push: scope:source.sql
              with_prototype:
                - match: (?=''')
                  pop: true
                - include: line-continuation-inside-block-string
                - include: escaped-unicode-char
                - include: escaped-char
                - include: constant-placeholder
        - match: '(?=\S)'
          set:
            - meta_scope: meta.string.python string.quoted.single.block.python
            - match: "'''"
              scope: punctuation.definition.string.end.python
              set: after-expression
            - include: line-continuation-inside-block-string
            - include: escaped-unicode-char
            - include: escaped-char
            - include: constant-placeholder
    # Triple-quoted string, bytes, no syntax embedding
    - match: ([bB])(''')
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.block.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.block.python
        - match: "'''"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-block-string
        - include: escaped-char
        - include: constant-placeholder

  string-quoted-single:
    # Single-line capital R raw string, unicode or not, no syntax embedding
    - match: '([uU]?R)('')'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
    # Single-line capital R raw string, bytes, no syntax embedding
    - match: '([bB]R|R[bB])('')'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
    # Single-line raw string, unicode or not, starting with a SQL keyword
    - match: '([uU]?r)('')(?={{sql_indicator}})'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.sql
          with_prototype:
            - match: '(?=''|\n)'
              pop: true
            - include: line-continuation-inside-string
            - include: constant-placeholder
    # Single-line raw string, unicode or not, treated as regex
    - match: '([uU]?r)('')'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.regexp.python
          with_prototype:
            - match: '(?=''|\n)'
              pop: true
            - include: line-continuation-inside-string
    # Single-line raw string, bytes, treated as regex
    - match: '([bB]r|r[bB])('')'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.regexp.python
          with_prototype:
            - match: '(?=''|\n)'
              pop: true
            - include: line-continuation-inside-string
    # Single-line raw f-string
    - match: ([fF]R|R[fF])(')
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - include: f-string-content
    # Single-line raw f-string, treated as regex
    - match: ([fF]r|r[fF])(')
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.regexp.python
          with_prototype:
            - match: (?='|\n)
              pop: true
            - include: line-continuation-inside-string
            - include: f-string-content-with-regex
    # Single-line f-string
    - match: ([fF])(')
      captures:
        1: storage.type.string.python
        2: meta.string.interpolated.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.interpolated.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: escaped-fstring-escape
        - include: escaped-unicode-char
        - include: escaped-char
        - include: line-continuation-inside-string
        - include: f-string-content
    # Single-line string, unicode or not, starting with a SQL keyword
    - match: '([uU]?)('')(?={{sql_indicator}})'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: line-continuation-inside-string
        - match: ''
          push: scope:source.sql
          with_prototype:
            - match: '(?=''|\n)'
              pop: true
            - include: escaped-unicode-char
            - include: escaped-char
            - include: line-continuation-inside-string
            - include: constant-placeholder
    # Single-line string, unicode or not
    - match: '([uU]?)('')'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: escaped-unicode-char
        - include: escaped-char
        - include: line-continuation-inside-string
        - include: constant-placeholder
    # Single-line string, bytes
    - match: '([bB])('')'
      captures:
        1: storage.type.string.python
        2: meta.string.python string.quoted.single.python punctuation.definition.string.begin.python
      push:
        - meta_content_scope: meta.string.python string.quoted.single.python
        - match: "'"
          scope: punctuation.definition.string.end.python
          set: after-expression
        - include: escaped-char
        - include: line-continuation-inside-string
        - include: constant-placeholder

  strings:
    # block versions must be matched first
    - include: string-quoted-double-block
    - include: string-quoted-double
    - include: string-quoted-single-block
    - include: string-quoted-single

  inline-for:
    - match: \b(?:(async)\s+)?(for)\b
      captures:
        1: storage.modifier.async.python
        2: keyword.control.loop.for.generator.python
      push:
        - include: comments
        - meta_scope: meta.expression.generator.python
        - match: \bin\b
          scope: keyword.control.loop.for.in.python
          pop: true
        - match: '(?=[)\]}])'
          scope: invalid.illegal.missing-in.python
          pop: true
        - include: illegal-names-pop
        - include: target-list

  inline-if:
    - match: \bif\b
      scope: keyword.control.conditional.if.python
    - match: \belse\b
      scope: keyword.control.conditional.else.python

  target-list:
    - match: ','
      scope: punctuation.separator.target-list.python
    - match: \(
      scope: punctuation.section.target-list.begin.python
      push:
        - include: comments
        - match: ','
          scope: punctuation.separator.target-list.python
        - match: \)
          scope: punctuation.section.target-list.end.python
          pop: true
        - include: target-list
        - include: name
    - include: name