%YAML 1.2
---

# AppArmor syntax definition for Sublime Text 3+ (>= 3092).

name: AppArmor

# AppArmor profiles do not have a proper file extension.
# We thus try to match these ones in a "best effort" way.
file_extensions:
  - abstraction
  - apparmor-profile
  - apparmor.profile

# AppArmor profiles do not have a proper shebang.
# We thus try to match as first line :
#   * an `abi` statement ;
#   * `include` statements ;
#   * `#include` statements (deprecated) ;
#   * a shebang with `apparmor_parser` ;
#   * a comment line containing the word "apparmor" (case-insensitive).
first_line_match: |-
  (?x)
  (?:^
    (?:abi\s+<abi\/[[:digit:]]+\.[[:digit:]]+>\s*,)|
    (?:\#?include\s+<[[:alnum:]_\/]+>)|
    (?:\#?include\s+"[[:alnum:]_\/]+")|
    (?:\#![ \t]*(?:\/usr(?:\/local)?)?\/s?bin\/apparmor_parser\b)|
    (?:\#.*\b[aA][pP]{2}[aA][rR][mM][oO][rR]\b)
  )

scope: source.apparmor
hidden: false


variables:
  identifier: (?:[[:alpha:]_][[:alnum:]_]*)
  capture_var_exp: (@)({)({{identifier}})(})

  profile_flags: |-
    (?x)
    \b(?:
      enforce|complain|kill|unconfined|
      audit|(?:chroot|namespace)_relative|(?:delegate|mediate)_deleted|
      (?:no_)?attach_disconnected|chroot_(?:no_)?attach
    )\b

  rule_qualifiers: \b(?:audit|deny|quiet|(?:no)?kill|owner)\b

  dbus_accesses: \b(send|receive|bind|eavesdrop)\b
  file_access_modes: |-
    (?x)
    \b(?:
      (?:[RrWwaLlMmkXx])|
      (?:(?:[Pp]|[Cc])[Xx])|(?:(?:[Pp]|[Cc])?(?:[IiUu])[Xx])
    )+\b
  ptrace_accesses: \b(?:rw?|w|read(?:by)?|trace(?:dby)?)\b
  signal_accesses: \b(?:rw?|w|read|write|send|receive)\b
  unix_accesses: |-
    (?x)
    \b(?:
      rw?|w|
      send|receive|
      create|bind|listen|accept|connect|shutdown|
      [gs]et(?:attr|opt)|
    )\b

  capabilities: |-
    (?x)
    \b(?:
      audit_(?:control|read|write)|block_suspend|bpf|checkpoint_restore|chown|
      dac_(?:override|read_search)|fowner|fsetid|ipc_(?:lock|owner)|kill|lease|linux_immutable|
      mac_(?:admin|override)|mknod|net_bind_service|net_(?:admin|broadcast|raw)|perfmon|
      set(?:fcap|[gu]id|pcap)|syslog|
      sys_(?:admin|boot|chroot|module|nice|pacct|ptrace|rawio|resource|time|tty_config)|
      wake_alarm
    )\b

  mount_flags: |-
    (?x)
    \b(?:
      ro|rw|
      (?:no)?(?:suid|dev|exec|mand|acl|user|i?version)|
      (?:no)?(?:dir|rel)?atime|
      a?sync|remount|dirsync|move|verbose|silent|loud|
      r?(?:bind|unbindable|private|slave|shared)|
      strictatime|
    )\b

  network_domains: |-
    (?x)
    \b(?:
      inet6?|unix|netlink|packet|
      alg|appletalk|ash|atm[ps]vc|ax25|bluetooth|bridge|caif|can|econet|ib|ieee802154|ipx|irda|
      isdn|iucv|key|llc|mpls|netbeui|netrom|nfc|phonet|pppox|rds|rose|rxrpc|security|sna|tipc|
      vsock|wanpipe|x25
    )\b
  network_types: \b(?:stream|dgram|raw|rdm|(?:seq)?packet|dccp)\b
  network_protocols: \b(tcp|udp|icmp)\b

  rlimits: |-
    (?x)
    \b(?:
      cpu|fsize|data|stack|core|rss|n?ofile|as|nproc|memlock|locks|sigpending|msgqueue|nice|
      rt(?:prio|time)
    )\b
  # We omit left word-boundary on purpose here as time suffixes can be next to values.
  rlimit_time_suffixes: |-
    (?x)
    (?:
      [um]s|(?:micro|milli)seconds?|
      s(?:ec(?:onds?)?)?|
      min(?:utes?)?|
      h(?:ours?)?|
      d(?:ays?)?|
      weeks?
    )\b

  signals: |-
    (?x)
    \b(?:
      hup|int|quit|ill|trap|abrt|bus|fpe|kill|usr[12]|segv|pipe|alrm|term|stkflt|chld|cont|sto?p|
      tt(?:in|ou)|urg|xcpu|xfsz|vtalrm|prof|winch|io|pwr|sys|emt|exists|rtmin\+\d{1,2}
    )\b


contexts:
  main:
    - include: preamble
    - include: profile

  prototype:
    - include: comments

  comments:
    # The negative look-ahead is required while we support deprecated "pound-include" statement.
    # See `includes`.
    - match: (#)(?!include\b)
      scope: punctuation.definition.comment.apparmor
      push:
        - meta_include_prototype: false
        - meta_scope: comment.line.apparmor

        - match: (?=$)
          pop: true

  line-continuation:
    - match: (?:(\\)(.*)$\r?\n)
      captures:
        1: punctuation.separator.continuation.apparmor
        2: invalid.illegal.trailing-character.apparmor

  variables:
    - match: (?:{{capture_var_exp}})
      captures:
        1: punctuation.definition.variable.apparmor
        2: punctuation.section.interpolation.begin.apparmor
        3: variable.other.readwrite.apparmor
        4: punctuation.section.interpolation.end.apparmor

  new-line:
    - match: (?:$\r?\n)
      pop: true

  force-pop:
    - match: ()
      pop: true

  bail-out-on-comma:
    - match: (?=,)
      pop: true

  bail-out-on-parens-end:
    - match: (?=\))
      pop: true

  bail-out-on-whitespace:
    - match: (?=\s)
      pop: true

  comma:
    - match: (,)
      scope: punctuation.separator.comma.apparmor
  _expect-comma:
    - match: (,)
      scope: punctuation.separator.comma.apparmor
      pop: true

  _expect-profile-name:
    - match: (?:{{capture_var_exp}}|({{identifier}}))+
      captures:
        1: punctuation.definition.variable.apparmor
        2: punctuation.section.interpolation.begin.apparmor
        3: variable.other.readwrite.apparmor
        4: punctuation.section.interpolation.end.apparmor
        5: entity.name.profile.apparmor
      pop: true

  quoted-string:
    # `prototype` won't be propagated, but `line-continuation` has to be consumed.
    - match: (")
      scope: punctuation.definition.string.begin.apparmor
      with_prototype:
        - match: (\\.)
          scope: constant.character.escape.apparmor
        - include: line-continuation
      push:
        - meta_include_prototype: false
        - meta_scope: string.quoted.double.apparmor meta.string.apparmor

        - match: (")
          scope: punctuation.definition.string.end.apparmor
          pop: true
  _expect-quoted-string:
    # `prototype` won't be propagated, but `line-continuation` has to be consumed.
    - match: (")
      scope: punctuation.definition.string.begin.apparmor
      with_prototype:
        - match: (\\.)
          scope: constant.character.escape.apparmor
        - include: line-continuation
      set:
        - meta_include_prototype: false
        - meta_scope: string.quoted.double.apparmor meta.string.apparmor

        - match: (")
          scope: punctuation.definition.string.end.apparmor
          pop: true

  file-glob:
    # Check whether this path starts with a '/' or variable expansion.
    # Don't consume them, yet.
    - match: (?={{capture_var_exp}}|/)
      push:
        - include: bail-out-on-whitespace
        - include: aare

    - include: quoted-string

  _expect-file-glob-and-comma:
    - match: (?={{capture_var_exp}}|/)
      set:
        - include: bail-out-on-whitespace
        - include: _expect-comma
        - include: aare

    - include: quoted-string

  aare:
    - include: quoted-string

    - match: (?:{{capture_var_exp}})
      captures:
        1: punctuation.definition.variable.apparmor
        2: punctuation.section.interpolation.begin.apparmor
        3: variable.other.readwrite.apparmor
        4: punctuation.section.interpolation.end.apparmor

    - match: (\\.)
      scope: string.regexp.apparmor constant.character.escape.regexp.apparmor

    - match: ({)
      scope: string.regexp.apparmor keyword.operator.quantifier.regexp.apparmor
      push:
        - match: (})
          scope: string.regexp.apparmor keyword.operator.quantifier.regexp.apparmor
          pop: true

        - match: (,)
          scope: string.regexp.apparmor keyword.operator.quantifier.regexp.apparmor

        - include: aare

    - match: (\[)
      scope: string.regexp.apparmor punctuation.definition.set.begin.regexp.apparmor
      push:
        - meta_scope: meta.set.regexp.apparmor

        - match: (\])
          scope: string.regexp.apparmor punctuation.definition.set.end.regexp.apparmor
          pop: true

        - match: (\S)(-)(\S)
          scope: string.regexp.apparmor
          captures:
            1: constant.other.range.regexp.apparmor
            2: punctuation.separator.sequence.regexp.apparmor
            3: constant.other.range.regexp.apparmor

        - include: aare

    - match: ([\?\*\^\[\]\{\}])
      scope: string.regexp.apparmor keyword.operator.quantifier.regexp.apparmor

    # Any other non-whitespace character is considered to be part of the "string".
    - match: ([^\s\x00])
      scope: string.regexp.apparmor

  magic-path:
    # `prototype` won't be propagated, but `line-continuation` has to be consumed.
    - match: (<)
      scope: punctuation.definition.string.begin.apparmor
      with_prototype:
        - match: (\\.)
          scope: constant.character.escape.apparmor
        - include: line-continuation
      push:
        - meta_include_prototype: false
        - meta_scope: string.quoted.other.lt-gt.apparmor meta.string.apparmor

        - match: (>)
          scope: punctuation.definition.string.end.apparmor
          pop: true

  includes:
    - match: (#?)(\binclude\b)
      captures:
        0: meta.preprocessor.include.apparmor keyword.control.import.include.apparmor
        1: invalid.deprecated.pound-include.apparmor
      push:
        - meta_scope: meta.preprocessor.include.apparmor

        - match: \b(if)\s+(exists)\b
          captures:
            1: keyword.control.conditional.apparmor
            2: keyword.operator.word.exists.apparmor

        - include: quoted-string
        - include: magic-path
        - include: new-line

  variable-assignment:
    - include: variables

    - match: (\+?=)
      scope: keyword.operator.assignment.apparmor
      push:
        - include: new-line
        - include: aare
  _expect-variable-assignment:
    - include: variables

    - match: (=)
      scope: keyword.operator.assignment.apparmor
      set:
        - include: new-line
        - include: aare

  preamble:
    # ABI
    - match: \b(abi)\b
      scope: meta.preprocessor.abi.apparmor keyword.control.import.abi.apparmor
      push:
        - meta_scope: meta.preprocessor.abi.apparmor

        - include: magic-path
        - include: _expect-comma

    # Alias
    - match: \b(alias)\b
      scope: storage.type.alias.apparmor
      push:
        # Absolute paths start with '/' and don't contain any space or null bytes.
        - match: (\/[^\s\x00]+)
          scope: string.unquoted.absolute-path.apparmor

        - match: (?:(->)|\b(to)\b)
          captures:
            1: keyword.operator.arrow.apparmor
            2: keyword.operator.word.to.apparmor
          set:
            # For this context, we expect an absolute path ending with a comma.
            - match: (\/)
              scope: string.unquoted.absolute-path.apparmor
              set:
                - include: _expect-comma
                - match: ([^\s\x00])
                  scope: string.unquoted.absolute-path.apparmor

    - include: includes
    - include: variable-assignment

  profile:
    - match: \b(profile)\b
      scope: storage.type.profile.apparmor keyword.declaration.profile.apparmor
      push: _expect-profile-name

    - include: profile-flags
    - include: profile-attachment
    - include: profile-body

  profile-attachment:
    - match: \b(xattrs)(=)
      captures:
        1: entity.name.tag.profile-xattrs.apparmor
        2: keyword.operator.assignment.apparmor
      push:
        - match: (\()
          scope: punctuation.section.parens.begin
          set:
            - meta_scope: meta.parens.profile-xattrs.apparmor

            - match: (\))
              scope: punctuation.section.parens.end
              pop: true

            - match: ({{identifier}})
              scope: string.unquoted.xattrs.apparmor

            - match: (\.)
              scope: punctuation.accessor.dot.apparmor

            - match: (=)
              scope: keyword.operator.assignment.apparmor
              push:
                - include: bail-out-on-parens-end
                - include: bail-out-on-whitespace
                - include: aare

    - include: file-glob

  profile-flags:
    - match: \b(flags)(=)
      captures:
        1: entity.name.tag.profile-flags.apparmor
        2: keyword.operator.assignment.apparmor
      push:
        - match: (\()
          scope: punctuation.section.parens.begin
          set:
            - meta_scope: meta.parens.profile-flags.apparmor

            - match: (\))
              scope: punctuation.section.parens.end
              pop: true

            - match: ({{profile_flags}})
              scope: constant.language.profile-flag.apparmor

  profile-body:
    - match: ({)
      scope: punctuation.section.block.begin.profile.apparmor
      push:
        - meta_scope: meta.block.profile.apparmor

        - match: (})
          scope: punctuation.section.block.end.profile.apparmor
          pop: true

        - include: profile-body-contexts
  _expect-profile-body:
    - match: ({)
      scope: punctuation.section.block.begin.profile.apparmor
      set:
        - meta_scope: meta.block.profile.apparmor

        - match: (})
          scope: punctuation.section.block.end.profile.apparmor
          pop: true

        - include: profile-body-contexts

  profile-body-contexts:
    - include: includes
    - include: subprofile
    - include: hat
    - include: rules

  subprofile:
    - match: \b(profile)\b
      scope: storage.type.profile.apparmor keyword.declaration.profile.apparmor
      push: [__subprofile-head-and-body, _expect-profile-name]

  __subprofile-head-and-body:
    - include: profile-attachment
    - include: profile-flags

    - include: _expect-profile-body

  hat:
    - match: \b(hat)\b
      scope: storage.type.hat.apparmor keyword.declaration.hat.apparmor
      push: [__hat-head-and-body, _expect-profile-name]

    - match: (\^)({{identifier}})
      captures:
        1: punctuation.definition.keyword.caret.apparmor
        2: entity.name.profile.hat.apparmor
      push:
        - include: profile-flags
        - include: _expect-profile-body

  __hat-head-and-body:
    - include: profile-flags
    - include: _expect-profile-body

  rules:
    - match: ({{rule_qualifiers}})
      scope: constant.language.rule-qualifier.apparmor

    - include: capabilities
    - include: change-profile
    - include: dbus
    - include: link
    - include: mount
    - include: network
    - include: pivot-root
    - include: ptrace
    - include: rlimit
    - include: signal
    - include: unix

    # File rules syntax is pretty permissive, so we match them at last.
    - include: file

  capabilities:
    - match: \b(capability)\b
      scope: entity.other.attribute-name.capability.apparmor
      push:
        - match: ({{capabilities}})
          scope: constant.language.capability.apparmor

        - include: _expect-comma

  change-profile:
    - match: \b(change_profile)\b
      scope: entity.other.attribute-name.change-profile.apparmor
      push:
        - match: \b((?:un)?safe)\b
          scope: constant.language.change-profile-exec-mode.apparmor

        - match: (->)
          scope: keyword.operator.arrow.apparmor
          set:
            - include: _expect-comma
            - include: aare

        - include: _expect-comma
        - include: aare

  dbus:
    - match: \b(dbus)\b
      scope: entity.other.attribute-name.dbus.apparmor
      push:
        - match: ({{dbus_accesses}})
          scope: constant.language.dbus-access.apparmor

        - match: (\()
          scope: punctuation.section.parens.begin
          push:
            - meta_scope: meta.parens.dbus-accesses.apparmor

            - match: (\))
              scope: punctuation.section.parens.end
              pop: true

            - match: ({{dbus_accesses}})
              scope: constant.language.dbus-access.apparmor

            - include: comma

        - include: __dbus-conds

        - match: \b(peer)(=)
          captures:
            1: entity.name.tag.dbus-peer-cond.apparmor
            2: keyword.operator.assignment.apparmor
          push:
            - match: (\()
              scope: punctuation.section.parens.begin
              set:
                - meta_scope: meta.parens.dbus-peer-conds.apparmor

                - match: (\))
                  scope: punctuation.section.parens.end
                  pop: true

                - include: __dbus-conds
                - include: comma

        - match: \b(bus)(=)
          captures:
            1: entity.name.tag.dbus-bus.apparmor
            2: keyword.operator.assignment.apparmor
          push:
            - match: \b(system|session)\b
              scope: constant.language.dbus-bus.apparmor
              pop: true

            - include: bail-out-on-whitespace
            - include: aare

        - include: _expect-comma

  __dbus-conds:
    - match: \b(path|interface|member|name|label)(=)
      captures:
        1: entity.name.tag.dbus-cond.apparmor
        2: keyword.operator.assignment.apparmor
      push:
        - match: (\()
          scope: punctuation.section.parens.begin
          set:
            - meta_scope: meta.parens.dbus-conds.apparmor

            - match: (\))
              scope: punctuation.section.parens.end
              pop: true

            - include: aare

        - include: bail-out-on-comma
        - include: bail-out-on-whitespace
        - include: aare

  link:
    - match: \b(link)\b
      scope: entity.other.attribute-name.link.apparmor
      push:
        - match: \b(subset)\b
          scope: entity.name.tag.link-subset.apparmor

        - match: (->)
          scope: keyword.operator.arrow.apparmor
          set: _expect-file-glob-and-comma

        - include: file-glob

  mount:
    - match: \b((?:re|u)?mount)\b
      scope: entity.other.attribute-name.mount.apparmor
      push:
        - match: \b(options)(?:(=)|\s+(in)\b)
          captures:
            1: entity.name.tag.mount-options.apparmor
            2: keyword.operator.assignment.apparmor
            3: keyword.operator.word.in.apparmor
          push:
            - match: (\()
              scope: punctuation.section.parens.begin
              set:
                - meta_scope: meta.parens.mount-flags.apparmor

                - match: (\))
                  scope: punctuation.section.parens.end
                  pop: true

                - match: ({{mount_flags}})
                  scope: constant.language.mount-flag.apparmor

                - include: comma

            - match: ({{mount_flags}})
              scope: constant.language.mount-flag.apparmor
              pop: true

        - match: \b(v?fstype)(?:(=)|\s+(in)\b)
          captures:
            1: entity.name.tag.mount-fstype.apparmor
            2: keyword.operator.assignment.apparmor
            3: keyword.operator.word.in.apparmor
          push:
            - include: bail-out-on-whitespace
            - include: aare

        - match: (->)
          scope: keyword.operator.arrow.apparmor
          set: _expect-file-glob-and-comma

        - include: file-glob

  network:
    - match: \b(network)\b
      scope: entity.other.attribute-name.network.apparmor
      push:
        - match: ({{network_domains}})
          scope: constant.language.network-domain.apparmor
          set:
            - match: ({{network_types}})
              scope: constant.language.network-type.apparmor
              set: _expect-comma

            - match: ({{network_protocols}})
              scope: constant.language.network-protocol.apparmor
              set: _expect-comma

            - include: _expect-comma

  pivot-root:
    - match: \b(pivot_root)\b
      scope: entity.other.attribute-name.pivot-root.apparmor
      push:
        - include: _expect-comma

        - match: \b(oldroot)(=)
          captures:
            1: entity.name.tag.pivot-root-oldroot.apparmor
            2: keyword.operator.assignment.apparmor
          push:
            - include: bail-out-on-whitespace
            - include: aare

        - match: (->)
          scope: keyword.operator.arrow.apparmor
          set:
            - include: _expect-comma
            - include: aare

        - include: aare

  ptrace:
    - match: \b(ptrace)\b
      scope: entity.other.attribute-name.ptrace.apparmor
      push:
        - match: ({{ptrace_accesses}})
          scope: constant.language.ptrace-access.apparmor

        - match: (\()
          scope: punctuation.section.parens.begin
          push:
            - meta_scope: meta.parens.ptrace-accesses.apparmor

            - match: (\))
              scope: punctuation.section.parens.end
              pop: true

            - match: ({{ptrace_accesses}})
              scope: constant.language.ptrace-access.apparmor

            - include: comma

        - match: \b(peer)(=)
          captures:
            1: entity.name.tag.ptrace-peer.apparmor
            2: keyword.operator.assignment.apparmor
          set:
            - include: _expect-comma
            - include: aare

        - include: _expect-comma

  rlimit:
    - match: (?:\b(set)\s+(rlimit)\b)
      captures:
        1: entity.other.attribute-name.rlimit.apparmor
        2: entity.other.attribute-name.rlimit.apparmor
      push:
        - match: ({{rlimits}})
          scope: constant.language.rlimit.apparmor
          set:
            - match: (<=)
              scope: keyword.operator.arithmetic.apparmor
              set:
                - match: \b(infinity)\b
                  scope: constant.language.infinity.apparmor
                  set: _expect-comma

                # time value
                - match: (?:(\d+)\s*({{rlimit_time_suffixes}}))
                  captures:
                    1: constant.numeric.integer.decimal.apparmor
                    2: constant.numeric.suffix.apparmor
                  set: _expect-comma

                # size value
                - match: (?:(\d+)([KMG]B?)?\b)
                  captures:
                    1: constant.numeric.integer.decimal.apparmor
                    2: constant.numeric.suffix.apparmor
                  set: _expect-comma

                # nice value
                - match: (-)(\d{1,2})
                  captures:
                    1: keyword.operator.arithmetic.apparmor
                    2: constant.numeric.integer.decimal.apparmor
                  set: _expect-comma

  signal:
    - match: \b(signal)\b
      scope: entity.other.attribute-name.signal.apparmor
      push:
        - match: ({{signal_accesses}})
          scope: constant.language.signal-access.apparmor

        - match: (\()
          scope: punctuation.section.parens.begin
          push:
            - meta_scope: meta.parens.signal-accesses.apparmor

            - match: (\))
              scope: punctuation.section.parens.end
              pop: true

            - match: ({{signal_accesses}})
              scope: constant.language.signal-access.apparmor

            - include: comma

        - match: \b(set)(=)
          captures:
            1: entity.name.tag.signal-set.apparmor
            2: keyword.operator.assignment.apparmor
          push:
            - match: (\()
              scope: punctuation.section.parens.begin
              set:
                - meta_scope: meta.parens.signal-list.apparmor

                - match: (\))
                  scope: punctuation.section.parens.end
                  pop: true

                - match: ({{signals}})
                  scope: constant.language.signal.apparmor

                - include: comma

        - match: \b(peer)(=)
          captures:
            1: entity.name.tag.signal-peer.apparmor
            2: keyword.operator.assignment.apparmor
          set:
            - include: _expect-comma
            - include: aare

        - include: _expect-comma

  unix:
    - match: \b(unix)\b
      scope: entity.other.attribute-name.unix.apparmor
      push:
        - match: ({{unix_accesses}})
          scope: constant.language.unix-access.apparmor

        - match: (\()
          scope: punctuation.section.parens.begin
          push:
            - meta_scope: meta.parens.unix-accesses.apparmor

            - match: (\))
              scope: punctuation.section.parens.end
              pop: true

            - match: ({{unix_accesses}})
              scope: constant.language.unix-access.apparmor

            - include: comma

        - match: \b(type|protocol|addr|label|attr|opt)(=)
          captures:
            1: entity.name.tag.unix-cond.apparmor
            2: keyword.operator.assignment.apparmor
          push:
            - match: (\()
              scope: punctuation.section.parens.begin
              set:
                - meta_scope: meta.parens.unix-conds.apparmor

                - match: (\))
                  scope: punctuation.section.parens.end
                  pop: true

                - include: comma
                - include: aare

            - include: bail-out-on-comma
            - include: bail-out-on-whitespace

            - include: aare

        - include: _expect-comma

        - match: \b(peer)(=)
          captures:
            1: entity.name.tag.unix-peer-cond.apparmor
            2: keyword.operator.assignment.apparmor
          set:
            - include: _expect-comma

            - match: (\()
              scope: punctuation.section.parens.begin
              push:
                - meta_scope: meta.parens.unix-peer-conds.apparmor

                - match: (\))
                  scope: punctuation.section.parens.end
                  pop: true

                - match: \b(addr|label)(=)
                  captures:
                    1: entity.name.tag.unix-peer-cond.apparmor
                    2: keyword.operator.assignment.apparmor
                  push:
                    - include: _expect-comma
                    - include: bail-out-on-parens-end

                    - include: aare

            - match: \b(addr|label)(=)
              captures:
                1: entity.name.tag.unix-peer-cond.apparmor
                2: keyword.operator.assignment.apparmor
              set:
                - include: _expect-comma
                - include: aare

  file:
    - match: \b(file)\b
      scope: entity.other.attribute-name.file.apparmor
      push:
        - include: __file
        - include: force-pop

    - include: __file

  __file:
    - match: ({{file_access_modes}})
      scope: constant.language.file-access-modes.apparmor
      push:
        - include: __transition
        - include: _expect-file-glob-and-comma

    - include: __transition
    - include: file-glob

  __transition:
    - include: _expect-comma

    - match: (->)
      scope: keyword.operator.arrow.apparmor
      set: [_expect-comma, _expect-profile-name]