const C = require("tree-sitter-c/grammar")


module.exports = grammar(C, {
  name: 'objc',

  extras: $ => [
    /\u00A0|\s|\\\r?\n/, // non-breaking space
    $.comment,
    $.pragma,
    $._ifdef_if_retain,
    $._ifdef_elif_ignore,
    $._ifdef_else_ignore,
    $._ifdef_endif_retain,
    $._ifdef_undef_retain,
  ],

  conflicts: ($, superclass) => superclass.concat([
    [$.struct_specifier],
    [$.union_specifier],
    [$.enum_specifier],
    [$.function_declarator],
    [$.type_descriptor],
    [$.superclass_reference],
    [$._expression, $.macro_type_specifier],
    [$._expression, $.generic_type_specifier],
    [$._declaration_specifiers],
    [$._declaration_specifiers, $.class_interface],
    [$.protocol_identifier, $._type_specifier],
    [$.protocol_identifier, $._parameterized_class_type_arguments],
    [$.declaration, $._argument_type_declarator],
  ]),

  supertypes: $ => [
    $._expression,
    $._statement,
    $._declarator,
    $._field_declarator,
    $._type_declarator,
    $._abstract_declarator,
  ],
  
  rules: {
    identifier: $ => /[$a-zA-Z_](\w|\$)*/,
    
    _top_level_item: ($, superclass) => choice(
      ...superclass.members.filter(member => member.name !== 'preproc_call'), // FIX: #endif
      $._import,
      $.class_interface,
      $.class_implementation,
      $.category_interface,
      $.category_implementation,
      $.protocol_declaration,
      $.protocol_forward_declaration,
      $.class_forward_declaration,
      $._ns_assume_nonnull_declaration,
      $.compatibility_alias_declaration,
    ),

    _name: $ => field('name', $.identifier),

    //
    // Imports
    //

    _import: $ => choice(
      $.preproc_import,
      $.module_import
    ),

    preproc_import: $ => seq(
      '#import',
      field('path',
        choice(
          $.system_lib_string,
          $.string_literal
        )
      )
    ),

    module_import: $ => seq(
      '@import', field('module', $.module_string)
    ),

    module_string: $ => /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/,


    //
    // Declarations
    //

    _ns_assume_nonnull_declaration: $ => token.immediate(/NS_ASSUME_NONNULL_BEGIN|NS_ASSUME_NONNULL_END|CF_EXTERN_C_BEGIN|CF_EXTERN_C_END/),

    compatibility_alias_declaration: $ => seq(
      '@compatibility_alias',
      field('class_name', $.identifier),
      field('alias_name', $.identifier),
      ';'
    ),

    protocol_forward_declaration: $ => prec(6, seq(
      '@protocol', commaSep1($._name), ';'
    )),

    class_forward_declaration: $ => seq(
      '@class', commaSep1(seq($._name, optional(choice($.protocol_qualifiers, $.parameterized_class_type_arguments)))), ';'
    ),
    
    class_interface: $ => choice(
      seq(
        optional($.class_interface_attribute_sepcifier),
        optional($.swift_name_attribute_sepcifier),
        repeat($.attribute_specifier),
        '@interface',
        choice(
          seq($._name, optional($.parameterized_class_type_arguments)),
          $.generics_type_reference
        ),
        optional($.superclass_reference),
        optional($.parameterized_class_type_arguments),
        $.protocol_qualifiers, // protocol_qualifiers must precede parameterized_class_type_arguments 
        optional($._instance_variables),
        optional($._interface_declaration),
        '@end',
      ),
      seq(
        optional($.class_interface_attribute_sepcifier),
        optional($.swift_name_attribute_sepcifier),
        repeat($.attribute_specifier),
        '@interface',
        choice(
          seq($._name, optional($.parameterized_class_type_arguments), optional($.protocol_qualifiers)),
          $.generics_type_reference
        ),
        optional($.superclass_reference),
        optional($._instance_variables),
        optional($._interface_declaration),
        '@end',
      ),
    ),

    category_interface: $ => seq(
      '@interface',
      choice(
        seq($._name, optional($.parameterized_class_type_arguments), optional($.protocol_qualifiers)),
        $.generics_type_reference
      ),
      '(', optional(field('category', $.identifier)),')',
      optional($.protocol_qualifiers),
      optional($._instance_variables),
      optional($._interface_declaration),
      '@end',
    ),

    protocol_declaration: $ => seq(
      optional($.swift_name_attribute_sepcifier),
      optional($.attribute_specifier),
      '@protocol', $._name,
      optional($.protocol_qualifiers),
      optional($._interface_declaration),
      '@end'
    ),

    superclass_reference: $ => seq(
      ':', 
      $._name,
      field('type_reference', optional($.generic_type_references)),
    ),
    
    protocol_qualifiers: $ => seq(
      '<', commaSep1($.protocol_identifier), '>'
    ),

    protocol_identifier: $ => prec.dynamic(5, alias($.identifier, 'protocol_identifier')),
    
    parameterized_class_type_arguments: $ => seq(
      '<', commaSep1($._parameterized_class_type_arguments), '>'
    ),

    _parameterized_class_type_arguments: $ => seq(
      optional(choice('__covariant', '__contravariant')),
      field('type', $.identifier),
      optional(seq(':', $.type_descriptor)), // superclass
    ),

    generics_type_reference: $ => seq(
      '__GENERICS',
      '(',
      seq($._name, repeat(seq(',', $._type_specifier))),
      ')',
    ),

    _instance_variables: $ => seq(
      '{', repeat($._instance_variable_declaration) ,'}'
    ),

    _instance_variable_declaration: $ => choice(
      $._visibility_specification,
      $.field_declaration,
    ),

    _field_declarator: ($, superclass) => choice(
      superclass,
      $.block_declarator,
    ),

    _visibility_specification: $ => choice(
      $.private,
      $.public,
      $.protected,
      $.package,
    ),

    private: $ => '@private',

    public: $ => '@public',

    protected: $ => '@protected',

    package: $ => '@package',

    _interface_declaration: $ => repeat1(
      choice(
        $._ns_assume_nonnull_declaration,
        $._interface_declaration_specifier,
        $.declaration,
        $.method_declaration,
        $.property_declaration,
        $._empty_declaration,
        $.type_definition,
        $.preproc_def,
        $.preproc_function_def,
      )
    ),

    _interface_declaration_specifier: $ => choice(
      $.optional,
      $.required,
    ),

    optional: $ => '@optional',

    required: $ => '@required',

    method_declaration: $ => seq(
      field('scope', $._class_member_scope),
      field('return_type', optional($._method_argument_type_specifier)),
      field('selector', $._method_selector),
      repeat(choice(
        $.attribute_specifier,
        $.swift_name_attribute_sepcifier,
      )),
      ';'
    ),

    _class_member_scope: $ => choice(
      $.class_scope,
      $.instance_scope
    ),

    class_scope: $ => '+',

    instance_scope: $ => '-',

    storage_class_specifier: ($, superclass) => choice(
      superclass,
      /FOUNDATION_EXPORT|FOUNDATION_EXTERN|FOUNDATION_STATIC_INLINE|NS_INLINE|UIKIT_EXTERN|CG_EXTERN|CG_INLINE/
    ),

    property_declaration: $ => seq(
      '@property',
      optional($.property_attributes),
      seq(
        optional($.attribute_specifier),
        repeat($.type_qualifier),
        field('type', $._type_specifier),
        optional($.attribute_specifier),
        repeat($.type_qualifier),
        commaSep1(field('declarator', $._declarator)),
      ),
      repeat($.attribute_specifier),
      optional($.swift_name_attribute_sepcifier),
      ';'
    ),

    property_attributes: $ => seq(
      '(', commaSep($._property_attribute), ')'
    ),

    _property_attribute: $ => choice(
      $.getter,
      $.setter,
      $.nonnull,
      $.nullable,
      $.null_resettable,
      $.unsafe_unretained,
      $.null_unspecified,
      $.direct,
      $.readwrite,
      $.readonly,
      $.strong,
      $.weak,
      $.copy,
      $.assign,
      $.retain,
      $.atomic,
      $.nonatomic,
      $.class,
      $.NS_NONATOMIC_IOSONLY,
      $.DISPATCH_QUEUE_REFERENCE_TYPE,
    ),
    
    class_interface_attribute_sepcifier: $ => token.immediate(/IB_DESIGNABLE|NS_ROOT_CLASS/),

    attribute_specifier: ($, superclass) => prec.dynamic(1, prec.left(choice(
      seq(
        choice('__attribute', '__attribute__'),
        '(',
        $.argument_list,
        ')'
      ),
      $.method_attribute_specifier,
      $.method_variadic_arguments_attribute_specifier,
      $.availability_attribute_specifier,
    ))),

    method_attribute_specifier: $ => token.immediate(/NS_DESIGNATED_INITIALIZER|NS_REQUIRES_SUPER|CF_RETURNS_RETAINED|CF_RETURNS_NOT_RETAINED|NS_REQUIRES_NIL_TERMINATION|NS_DIRECT/),
    
    method_variadic_arguments_attribute_specifier: $=> seq(
      choice('NS_FORMAT_FUNCTION', 'CF_FORMAT_FUNCTION'),
      '(',
      $.number_literal,
      ',',
      $.number_literal,
      ')'
    ),

    availability_attribute_specifier: $ => prec(1, choice(
      'NS_UNAVAILABLE',
      'DEPRECATED_ATTRIBUTE',
      'UI_APPEARANCE_SELECTOR',
      'UNAVAILABLE_ATTRIBUTE',
      seq(
        choice('NS_AVAILABLE', '__IOS_AVAILABLE', 'NS_AVAILABLE_IOS', 
          'API_AVAILABLE', 'API_UNAVAILABLE', 'API_DEPRECATED', 
          'NS_ENUM_AVAILABLE_IOS', 'NS_DEPRECATED_IOS', 'NS_ENUM_DEPRECATED_IOS', 
          'DEPRECATED_MSG_ATTRIBUTE', '__deprecated_msg', '__deprecated_enum_msg', 
          'NS_SWIFT_UNAVAILABLE', 'NS_EXTENSION_UNAVAILABLE_IOS', 'NS_CLASS_AVAILABLE_IOS', 'NS_CLASS_DEPRECATED_IOS'),
        '(',
        commaSep1(choice(field('message', $.string_literal), $.platform_version)),
        ')'
      ),
    )),
    
    platform_version: $ => choice(
      field('version', /\d+_\d+/), // 8_0
      field('version', $.number_literal), // 11.0
      field('platform', $.platform), // ios, macos, tvos
      seq(
        field('platform', $.platform),
        '(',
        field('version', commaSep1(choice(
          /\d+_\d+/, // 8_0
          $.number_literal, // 11.0
          $.identifier, // API_TO_BE_DEPRECATED macro
        ))),
        ')'
      )
    ),
    
    platform: $ => choice(
      'ios', 'tvos', 'macos', 'macosx', 'watchos'
    ),

    swift_name_attribute_sepcifier: $ => choice(
      'NS_REFINED_FOR_SWIFT',
      seq(
        'NS_SWIFT_NAME',
        '(',
        field('class', optional($.identifier)),
        field('method', $.identifier),
        field('parameters', optional(seq(
          '(',
          repeat(
            seq(repeat($._type_specifier), ':'),
          ),
          ')',
        ))),
        ')'
      ),
    ),

    getter: $ => seq(
      'getter', '=', $._name
    ),

    setter: $ => seq(
      'setter', '=', seq($._name, optional(':'))
    ),

    nonnull: $ => 'nonnull',

    nullable: $ => 'nullable',

    null_resettable: $ => 'null_resettable',

    unsafe_unretained: $ => 'unsafe_unretained',

    null_unspecified: $ => 'null_unspecified',

    direct: $ => 'direct',
    
    readwrite: $ => 'readwrite',

    readonly: $ => 'readonly',

    strong: $ => 'strong',

    weak: $ => 'weak',

    copy: $ => 'copy',

    assign: $ => 'assign',

    retain: $ => 'retain',

    atomic: $ => 'atomic',

    nonatomic: $ => 'nonatomic',

    class: $ => 'class',

    NS_NONATOMIC_IOSONLY: $ => 'NS_NONATOMIC_IOSONLY',

    DISPATCH_QUEUE_REFERENCE_TYPE: $=> 'DISPATCH_QUEUE_REFERENCE_TYPE',

    //
    // Implementation
    //
    class_implementation: $ => seq(
      optional($.attribute_specifier),
      '@implementation', $._name, optional($.superclass_reference), optional('NS_UNAVAILABLE'),
      optional($._instance_variables),
      optional($._implementation_definition),
      '@end'
    ),

    category_implementation: $ => seq(
      optional($.attribute_specifier),
      '@implementation', $._name, '(', field('category', $.identifier),')',
      optional($._implementation_definition),
      '@end'
    ),

    _implementation_definition: $ => repeat1(
      choice(
        $._ns_assume_nonnull_declaration,
        $.function_definition,
        $.declaration,
        $.synthesize_definition,
        $.dynamic_definition,
        $.method_definition,
        $.type_definition,
        $.preproc_def,
        $.preproc_function_def,
      )
    ),

    synthesize_definition: $ => seq(
      '@synthesize', commaSep1($.synthesize_property), ';'
    ),

    synthesize_property: $ => seq(
      field('property', $.identifier),
      optional(seq('=', field('instance_variable', $.identifier)))
    ),

    dynamic_definition: $ => seq(
      '@dynamic', commaSep1(field('property', $.identifier)), ';'
    ),

    method_definition: $ => seq(
      field('scope', $._class_member_scope),
      field('return_type', optional($._method_argument_type_specifier)),
      field('selector', $._method_selector),
      optional($.attribute_specifier),
      optional(';'),
      field('body', $.compound_statement),
      optional(';'),
    ),

    _method_selector: $ => choice(
      $._unary_selector,
      seq(
        $.keyword_selector,
        optional(seq(',', '...')),
      ),
    ),

    _unary_selector: $ => $.identifier, // naming

    keyword_selector: $ => repeat1($.keyword_declarator),

    keyword_declarator: $ => prec.right(seq(
      field('keyword', optional($.identifier)), // naming
      ':',
      field('type', optional($._method_argument_type_specifier)),
      $._name,
      optional('__unused')
    )),
    
    _method_argument_type_specifier: $ => prec(1, seq(
      '(',
        $._argument_type_declarator,
      ')',
      optional($.attribute_specifier),
    )),
    
    _argument_type_declarator: $ => prec(10, seq(
      optional(choice($.nullable, $.nonnull)),
      optional('NS_NOESCAPE'),
      $._declaration_specifiers,
      optional(field('declarator', choice(
        $._declarator,
        $._abstract_declarator
      ))),
      repeat($.attribute_specifier),
    )),
    
    // Type specifiers

    type_definition: ($, superclass) => prec(1, seq(
      optional($.attribute_specifier),
      optional($.swift_name_attribute_sepcifier),
      'typedef',
      optional($.attribute_specifier),
      repeat($.type_qualifier),
      field('type', $._type_specifier),
      optional($.attribute_specifier),
      repeat($.type_qualifier),
      optional(field('declarator', $._type_declarator)), // NS_ENUM optional
      optional(choice(field('attributes', $.identifier), $.attribute_specifier)),
      ';'
    )),
    
    _type_declarator: ($, superclass) => choice(
      superclass,
      $.block_declarator,
    ),
    
    _abstract_declarator: ($, superclass) => choice(
      superclass,
      $.block_abstract_declarator,
    ),

    enum_specifier: ($, superclass) => choice(
      seq(
        'enum',
        choice(
          seq(
            field('name', optional($._type_identifier)),
            field('superclass', optional(seq(':', $._type_specifier))),
            field('body', optional($.enumerator_list))
          ),
          field('body', $.enumerator_list)
        )
      ),
      seq(
        choice('NS_ENUM', 'NS_ERROR_ENUM', 'NS_OPTIONS'),
        '(',
        field('type', $._type_specifier),
        optional(seq(',', field('name', $._type_identifier))),
        ')',
        field('body', optional($.enumerator_list)),
      )
    ),

    enumerator: ($, superclass) => seq(
      field('name', $.identifier),
      optional(seq('=', field('value', $._expression))),
      repeat(choice($.attribute_specifier, $.swift_name_attribute_sepcifier)),
    ),

    _type_specifier: ($, superclass) => choice(
      superclass,
      $.id,
      $.SEL,
      $.IMP,
      $.Class,
      $.BOOL,
      $.auto,
      $.instancetype,
      $.typeof_specifier,
      $.atomic_specifier,
      $.generic_type_specifier,
    ),

    typeof_specifier: $ => seq(
      field('operator', choice('typeof', '__typeof', '__typeof__')),
      '(',
      field('type', $._expression),
      ')',
    ),

    // https://en.cppreference.com/w/c/language/atomic
    // Note: fail to match macro_type_specifier rule, because _Atomic is also a storage_class_specifier
    atomic_specifier: $ => prec(-1, seq(
      field('operator', '_Atomic'),
      '(',
      field('type', $.type_descriptor),
      ')'
    )),

    generic_type_specifier: $ => prec.dynamic(1, choice(
      seq(
        field('class_name', choice($._type_identifier, $.id, $.Class)),
        field('type_reference', repeat1(choice($.protocol_qualifiers, $.generic_type_references))),
      )
    )),

    generic_type_references: $ => seq(
      '<', commaSep1($.type_descriptor), '>'
    ),

    struct_specifier: ($, superclass) => choice(
      seq(
        'struct',
        optional($.attribute_specifier),
        choice(
          seq(
            field('name', $._type_identifier),
            optional($.superclass_reference),
            field('body', optional($.field_declaration_list))
            ),
          field('body', $.field_declaration_list)
          ),
      ),
      seq(
        'struct',
        optional($.attribute_specifier),
        field('name', optional($.identifier)),
        field('body', seq('@defs', '(', field('class_name', $.identifier),')'))
      )
    ),
    
    type_qualifier: ($, superclass) => choice(
      superclass,
      'in',
      'out',
      'inout',
      'bycopy',
      'byref',
      'oneway',
      '_Nullable',
      '_Nonnull',
      '_Nullable_result',
      '_Null_unspecified',
      '__autoreleasing',
      '__nullable',
      '__nonnull',
      '__strong',
      '__weak',
      '__bridge',
      '__bridge_transfer',
      '__bridge_retained',
      '__unsafe_unretained',
      '__block',
      '__kindof',
      '__unused',
      '_Complex',
      '__complex',
      'IBOutlet',
      'IBInspectable',
      'NS_VALID_UNTIL_END_OF_SCOPE',
    ),

    //
    // Block
    // https://opensource.apple.com/source/clang/clang-703.0.31/src/tools/clang/docs/BlockLanguageSpec.rst.auto.html
    // How Do I Declare A Block in Objective-C?
    // https://stackoverflow.com/questions/9201514/block-declaration-syntax-list
    /**
      As a local variable:
      returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

      As a property:
      @property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);

      As a method parameter:
      - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;

      As an argument to a method call:
      [someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

      As a parameter to a C function:
      void SomeFunctionThatTakesABlock(returnType (^blockName)(parameterTypes));

      As a typedef:
      typedef returnType (^TypeName)(parameterTypes);
      TypeName blockName = ^returnType(parameters) {...};

      As a cast type specifier:
      (returnType (^)(parameterTypes))anotherBlock;
    **/

    declaration: $ => seq(
      optional($.swift_name_attribute_sepcifier),
      $._declaration_specifiers,
      commaSep1(field('declarator', choice(
        $._declarator,
        $.init_declarator
      ))),
      optional($.attribute_specifier),
      optional($.type_qualifier),
      ';'
    ),
    
    _declarator: ($, superclass) => choice(
      superclass,
      $.block_declarator,
    ),
    
    block_abstract_declarator: $ => seq(
      '(',
      optional('NS_NOESCAPE'),
      optional($.type_qualifier),
      '^',
      optional($.type_qualifier),
      ')',
      field('parameters', $.parameter_list),
    ),

    block_declarator: $ => seq(
      '(',
      '^',
      choice(
        seq(
          repeat($.type_qualifier),
          field('declarator', $.identifier),
          repeat($.type_qualifier),
        ),
        field('return_type', $.block_declarator),
      ),
      ')',
      field('parameters', $.parameter_list),
    ),

    block_expression: $ => seq(
      '^',
      field('declarator', optional(choice($.type_descriptor, $.type_qualifier))),
      field('parameters', optional($.parameter_list)),
      $.compound_statement
    ),

    //
    // Types
    //
    self: $ => 'self',

    super: $ => 'super',

    nil: $ => 'nil',

    id: $ => 'id',
    
    instancetype: $ => 'instancetype',

    Class: $ => 'Class',

    Method: $ => 'Method',

    SEL: $ => 'SEL',

    IMP: $ => 'IMP',

    BOOL: $ => 'BOOL',
    
    auto: $ => '__auto_type',

    //
    // Statements
    //
    _non_case_statement: ($, superclass) => choice(
      superclass,
      $.autoreleasepool_statement,
      $.synchronized_statement,
      $.for_in_statement,
      $.try_catch_statement,
      $.throw_statement,
    ),

    autoreleasepool_statement: $ => prec.right(seq(
      '@autoreleasepool',
      field('consequence', $._statement),
    )),

    synchronized_statement: $ => prec.right(seq(
      '@synchronized',
      field('condition', $.parenthesized_expression),
      field('consequence', $._statement),
    )),

    for_in_statement: $ => prec.dynamic(1, seq(
      'for',
      '(',
      field('initializer', $._argument_type_declarator),
      'in',
      field('loop', $._expression),
      ')',
      $._statement
    )),

    try_catch_statement: $ => prec.right(seq(
      '@try',
      field('body', $._statement),
      repeat(seq(
        '@catch',
        field('declaration', seq('(', choice($.parameter_declaration, '...'), ')')),
        field('catch', $._statement),
      )),
      optional(seq(
        '@finally',
        field('finally', $._statement)
      ))
    )),

    throw_statement: $ => seq(
      '@throw',
      optional(choice($._expression, $.comma_expression)),
      ';'
    ),


    //
    // Expressions
    //
    _expression: ($, superclass) => choice(
      superclass,
      $.self,
      $.super,
      $.nil,
      $.YES,
      $.NO,
      $.message_expression,
      $.selector_expression,
      $.protocol_expression,
      $.encode_expression,
      $.number_expression,
      $.string_expression,
      $.object_expression,
      $.dictionary_expression,
      $.array_expression,
      $.boolean_expression,
      $.block_expression,
      $.available_expression,
      $.statement_expression,
      $.va_arg_expression,
    ),

    _assignment_left_expression: ($, superclass) => choice(
      superclass,
      $.self,
    ),

    message_expression: $ => seq(
      '[',
      field('receiver', $._receiver),
      field('selector', $._message_selector),
      ']'
    ),

    _receiver: $ => choice(
      $._type_specifier,
      $._expression,
    ),

    _message_selector: $ => choice(
      field('selector', $.identifier),
      $.keyword_argument_list
    ),

    keyword_argument_list: $ => repeat1($.keyword_argument),

    keyword_argument: $ => seq(
      optional(field('keyword', $.identifier)),
      ':',
      field('argument', choice(
        $._expression,
        $._variadic_arguments,
      ))
    ),

    _variadic_arguments: $ => prec(1, commaSep1($._expression)),

    selector_expression: $ => seq(
      '@selector', '(', $._selector_name, ')'
    ),

    _selector_name: $ => choice(
      $._name,
      repeat1($._keyword_name)
    ),

    _keyword_name: $ => choice(
      seq($._name, ':'),
      ':'
    ),

    // https://opensource.apple.com/source/clang/clang-703.0.31/src/tools/clang/docs/ObjectiveCLiterals.rst.auto.html
    // objc-at-expression : '@' (string-literal | encode-literal | selector-literal | protocol-literal | object-literal)
    protocol_expression: $ => seq(
      '@protocol', '(', $.identifier, ')'
    ),

    encode_expression: $ => seq(
      '@encode', '(', $.type_descriptor, ')'
    ),
    
    number_expression: $ => prec(1, seq(
      '@',
      choice(
        seq(
          '(',
          choice(
            alias($.number_literal, 'number_literal'),
            seq("'", /[A-Za-z0-9]/, "'"),
          ),
          ')',
        ),
        seq(
          choice(
            alias($.number_literal, 'number_literal'),
            seq("'", /[A-Za-z0-9]/, "'"),
          ),
        )
      )
    )),

    string_expression: $ => seq(
      seq('@', alias($.string_literal, 'string_literal')),
      repeat(seq(optional('@'), alias($.string_literal, 'string_literal'))),
    ),

    object_expression: $ => seq(
      '@', '(', optional($._expression) , ')'
    ),

    dictionary_expression: $ => seq(
      '@', '{', optional($._dictionary_key_value_list), '}'
    ),

    _dictionary_key_value_list: $ => seq(
      commaSep1($._dictionary_key_value_pair),
      optional(',')
    ),

    _dictionary_key_value_pair: $ => seq(
      field('key', $._expression),
      ':',
      field('value', $._expression),
    ),

    array_expression: $ => seq(
      '@', '[', optional(commaSep1($._expression)), optional(',') ,']'
    ),
    
    YES: $ => 'YES',
    
    NO: $ => 'NO',

    boolean_expression: $ => seq(
      '@', choice($.YES, $.NO, '__objc_no', '__objc_yes')
    ),

    available_expression: $ => seq(
      choice('__builtin_available', '@available'),
      '(',
      commaSep1(seq(
          field('platform', $.identifier),
          field('version', /[0-9]+(\.[0-9]+)*/),
      )),
      optional(seq(',', '*')),
      ')',
    ),

    statement_expression: $ => seq(
      '(',
      field('body', $._statement),
      ')',
    ),

    va_arg_expression: $ => seq(
      'va_arg',
      '(',
      field('va_list', $._expression),
      ',',
      field('type', $.type_descriptor),
      ')'
    ),

    _preproc_expression: ($, superclass) => choice(
      $.preproc_has_include,
      superclass,
    ),

    preproc_has_include: $ => seq(
      '__has_include',
      '(',
      field('path', choice(
        $.string_literal,
        $.system_lib_string,
        $.module_string,
      )),
      ')'
    ),

    conditional_expression: ($, superclass) => prec.right(-2, choice(
      superclass,
      seq(
        field('condition', $._expression),
        '?',
        ':',
        field('alternative', $._expression)
      ),
    )),

    cast_expression: $ => prec(C.PREC.CAST, seq(
      '(',
      field('type', choice($.type_descriptor, $.block_abstract_declarator)),
      ')',
      field('value', $._expression)
    )),

    //
    // Lexical Structure
    // /#[ \t]*(import|include|pragma|if|elif|else|endif|define|undef|line|warning|error)[^\r\n]*\r?\n/
    //
    pragma: $ => token(
      /#[ \t]*(pragma|warning|error)[^\r\n]*\r?\n/
    ),

    _ifdef_if_retain: $ => token(choice(
      /#if[^\r\n]*\r?\n/,
      seq(
        /#if[^\r\\\n]*\\\r?\n/,
        repeat(/[^\r\\\n]*\\\r?\n/),
        /[^#\\\n]+\r?\n/
      ),
      seq(
        /#if[^\r\n]*\r?\n/,
        /#else[ \t]*\r?\n/,
      )
    )),

    _ifdef_elif_ignore: $ => token(seq(
      /#elif[^\r\n]*\r?\n/,
      repeat(choice(
        /([ \t]*[^#][^#]*)?\r?\n/,
        /[ \t]*#[ \t]*(elif|else|import|include|pragma|define|undef|line|warning|error).*\r?\n/,
      )),
      /[ \t]*#endif/,
    )),

    _ifdef_else_ignore: $ => token(choice(
      '#else',
      seq(
        /#else[ \t]*\r?\n/,
        repeat(choice(
          /([ \t]*[^#][^#]*)?\r?\n/,
          /[ \t]*#[ \t]*(import|include|pragma|define|undef|line|warning|error).*\r?\n/,
        )),
        /[ \t]*#endif/,
      )
    )),

    _ifdef_endif_retain: $ => token(
      /#endif[^\r\n]*\r?\n?/,
    ),
    
    _ifdef_undef_retain: $ => token(
      /#undef[^\r\n]*\r?\n?/,
    ),

  }
});

function commaSep (rule) {
  return optional(commaSep1(rule))
}

function commaSep1(rule) {
  return seq(rule, repeat(seq(',', rule)));
}