/** * @file Objective-C++ grammar for tree-sitter * @author Kieran Klukas * @license MIT * * Objective-C++ is C++ plus a bolt-on object model. Rather than restate the * ~3500 lines of C++ (templates, overloads, fold expressions, concepts) this * grammar inherits `tree-sitter-cpp` and adds only the Objective-C half: * interfaces, protocols, properties, message sends, blocks, boxed literals * and the `@`-prefixed statements. * * The same file covers plain Objective-C (`.m`). C++ constructs simply never * appear there, and the inherited rules cost nothing when unused. */ /// // @ts-check const CPP = require('tree-sitter-cpp/grammar'); const C = require('tree-sitter-c/grammar'); // tree-sitter-cpp does not re-export its precedence table, so the few levels // we need to sit next to are restated here. For reference, C uses // CALL 15, FIELD 16, SUBSCRIPT 17, and C++ adds LAMBDA 18. const PREC = { // Message sends bind like calls: `[a b].c` and `[a b][0]` must postfix. MESSAGE: 15, // A block literal is a primary expression; keep it above binary `^` (xor). BLOCK: 16, // `@interface Foo (Bar)` — the parens are a category, outranking the // parenthesised declarator C++ would otherwise find there. CATEGORY: 20, // `@implementation Foo { ... }` — the brace opens an ivar block, not the // free-floating compound statement that C permits at top level. IVARS: 20, }; /** * Attributes legal inside `@property (...)`. * `direct` and `class` are clang extensions; `null_resettable` is nullability. */ const PROPERTY_ATTRIBUTES = [ 'assign', 'atomic', 'class', 'copy', 'direct', 'nonatomic', 'nonnull', 'null_resettable', 'nullable', 'readonly', 'readwrite', 'retain', 'strong', 'unsafe_unretained', 'weak', ]; /** * ARC ownership and nullability qualifiers. These are legal anywhere `const` * is, so they join the inherited `type_qualifier`. */ const OBJC_TYPE_QUALIFIERS = [ // Interface Builder markers. `IBOutlet` and friends expand to nothing and // sit where a qualifier would; they are mixed case, so the general macro // rule cannot see them. 'IBOutlet', 'IBOutletCollection', 'IBInspectable', 'IBDesignable', '__autoreleasing', '__block', '__bridge', '__bridge_retained', '__bridge_transfer', '__kindof', '__nonnull', '__null_unspecified', '__nullable', '__strong', '__unsafe_unretained', '__unused', '__used', '__weak', '_Nonnull', '_Null_unspecified', '_Nullable', ]; /** * Parameter direction and bare nullability keywords. Unlike the list above * these are only legal inside the parentheses of a method type, and must stay * out of the general qualifier set: a global `in` would make the separator in * `for (id x in xs)` indistinguishable from a qualifier. */ const OBJC_METHOD_TYPE_QUALIFIERS = [ 'bycopy', 'byref', 'in', 'inout', 'nonnull', 'null_unspecified', 'nullable', 'oneway', 'out', ]; /** * The Objective-C declaration forms, shared between `_top_level_item` and * `_block_item`. A top-level `#if` wraps block items, so anything reachable * at file scope has to be reachable in both or it breaks inside a guard. * * @param {GrammarSymbols} $ */ const OBJC_DECLARATIONS = $ => [ $.objc_class_interface, $.objc_class_implementation, $.objc_protocol_declaration, $.objc_class_forward_declaration, $.objc_protocol_forward_declaration, $.objc_compatibility_alias, $.objc_module_import, ]; /** * The Objective-C statement forms, legal wherever a statement is. * * @param {GrammarSymbols} $ */ const OBJC_STATEMENTS = $ => [ $.objc_autoreleasepool_statement, $.objc_synchronized_statement, $.objc_try_statement, $.objc_throw_statement, $.objc_for_in_statement, ]; module.exports = grammar(CPP, { name: 'objcpp', externals: ($, original) => original.concat([ $.objc_macro_argument_text, ]), conflicts: ($, original) => original.concat([ // `[a b]` (message) vs `[a]{}` (lambda) vs `a[b]` (subscript). All three // open with `[`, so a captured name and a receiver expression have to be // carried side by side until what follows the bracket settles it. [$._lambda_capture_identifier, $.expression], // A receiver may be a bare type (`[NSString class]`) or a bare variable // (`[str length]`); both reduce from `identifier`. [$._objc_receiver, $.expression], // `void (^)(int)` (abstract, a type) vs `void (^cb)(int)` (named). The // caret is shared; the name that decides only arrives afterwards. C has // the identical standoff between its pointer declarators. [$.block_pointer_declarator, $.abstract_block_pointer_declarator], // `@interface Foo ` — generic parameter list or protocol list? Both // are legal grammar here, so keep both stacks alive. [$.objc_type_parameter, $.objc_protocol_reference_list], // With `[[` split into two tokens, a doubled bracket can open a C++ // attribute, a lambda nested in a lambda capture, or a message send whose // receiver is itself a message. All readings must survive until the // contents decide. [$.attribute, $._lambda_capture_identifier], [$.attribute, $.expression], // A leading `__attribute__` on a class is also a declaration modifier for // whatever might otherwise follow; only `@interface` settles it. [$._declaration_modifiers, $.objc_class_interface], ]), rules: { // Preprocessor conditionals whose branches hold class-body items rather // than top-level ones. Apple's headers gate properties and methods behind // `#if TARGET_OS_IPHONE` constantly, and without these the `#if` ends the // useful parse of the interface. C parameterises the same helper for // struct fields and enumerators; these are the Objective-C equivalents. ...C.preprocIf('_in_objc_interface', $ => $._objc_interface_item), ...C.preprocIf('_in_objc_implementation', $ => $._objc_implementation_item), // And one whose branches hold top-level items, so that a macro guarded by // an include guard still parses. C's own `preproc_if` is defined over // *block* items, which are equally the contents of every compound // statement; putting macros in there would turn `EXPECT_TRUE(x);` inside // a function into a macro invocation rather than a call. `_top_level_item` // filters C's variants out in favour of these. ...C.preprocIf('_in_objc_top_level', $ => $._top_level_item), // --------------------------------------------------------------- // Entry points // --------------------------------------------------------------- /** * Carries the Objective-C declarations and the macro forms, and swaps C's * preprocessor conditionals for variants whose branches hold top-level * items rather than block items. * * That swap is what lets a macro inside an include guard parse without * also admitting one inside every function body. */ _top_level_item: ($, original) => choice( ...original.members.filter(m => m.name !== 'preproc_if' && m.name !== 'preproc_ifdef'), alias($.preproc_if_in_objc_top_level, $.preproc_if), alias($.preproc_ifdef_in_objc_top_level, $.preproc_ifdef), ...OBJC_DECLARATIONS($), $.objc_macro_invocation, $.objc_enum_definition, ), /** * The branch body is optional, which C does not allow. * * Preprocessor-heavy C++ splits an `if` from its alternative across a * conditional boundary all the time: * * #if defined(__MAC_10_13) * if (available) { useModern(); } * else * #endif * { useLegacy(); } * * A tree-sitter tree is strictly nested, so an `else` inside the guard * cannot own a body outside it, and the construct is unparseable as * written. Letting the clause stand without a body makes both halves parse * as siblings instead, which highlights correctly even though the tree no * longer records that they belong together. `prec.right` keeps an ordinary * `else { ... }` attached to its body. * * The cost is that a bodyless `else` no longer registers as an error. For * a highlighter that is the right side to err on: valid code is never * mis-parsed, only invalid code is tolerated. */ else_clause: $ => prec.right(seq('else', optional($.statement))), /** * Also carries the class declarations and the statements. * * Deliberately *not* the macro forms. A block item is the content of every * compound statement as well as of C's `preproc_if`, so a macro admitted * here would swallow every `EXPECT_TRUE(x);` and `RCT_ASSERT(y);` in a * function body, turning a call into a macro invocation with no error to * show for it. Guarded macros are handled by `preproc_if_in_objc_top_level` * instead. */ _block_item: ($, original) => choice( original, ...OBJC_DECLARATIONS($), ...OBJC_STATEMENTS($), ), _non_case_statement: ($, original) => choice( original, ...OBJC_STATEMENTS($), ), _expression_not_binary: ($, original) => choice( original, $.message_expression, $.block_expression, $.objc_selector_expression, $.objc_protocol_expression, $.objc_encode_expression, $.objc_available_expression, $.objc_string_literal, $.objc_boxed_expression, $.objc_array_literal, $.objc_dictionary_literal, $.objc_super, ), // --------------------------------------------------------------- // Types // --------------------------------------------------------------- type_specifier: ($, original) => choice( original, $.objc_builtin_type, $.objc_typeof_specifier, $.objc_enum_specifier, ), /** * Apple's enum macros, which are how essentially every enumeration in the * system frameworks is spelled: * * typedef NS_ENUM(NSInteger, SKProductPeriodUnit) { ... }; * typedef NS_OPTIONS(NSUInteger, MTLResourceUsage) { ... }; * * The macro expands to an enum with an explicit backing type, so it is * modelled as a type specifier rather than left to the generic macro * fallback, which cannot reach a type position. * * Matched by the `_ENUM` / `_OPTIONS` suffix rather than a fixed list, so * `CF_ENUM`, `NS_CLOSED_ENUM`, `NS_ERROR_ENUM` and every vendor's variant * work without enumerating them. The narrow suffix keeps the token from * shadowing ordinary all-caps type names. */ objc_enum_specifier: $ => prec.right(seq( field('macro', $.objc_enum_macro_name), '(', field('backing_type', $.type_descriptor), optional(seq(',', field('name', $._type_identifier))), ')', optional(field('body', $.enumerator_list)), )), objc_enum_macro_name: _ => token(prec(2, /[A-Z][A-Z0-9_]*_(ENUM|OPTIONS)/)), /** * Allows an availability macro between an enum member and its value: * * MTLTextureTypeCubeArray API_AVAILABLE(macos(10.11)) = 6, * * Apple annotates enum members this way constantly. Because the macro sits * inside the enum body, one of them takes the entire surrounding * `typedef NS_ENUM(...) { ... }` down with it, which is why this single * position accounts for so many failed headers. */ enumerator: $ => seq( field('name', $.identifier), repeat($.objc_macro_invocation), optional(seq('=', field('value', $.expression))), ), /** * `typedef NS_ENUM(NSInteger, Unit) { ... };` * * The macro supplies the typedef name itself, so unlike an ordinary * `typedef` there is no trailing declarator. C's `type_definition` * insists on one, hence this variant. The form that *does* name a * declarator still goes through `type_definition` as usual. * * A trailing availability macro after the closing brace is common enough * to admit here too: `} API_AVAILABLE(macos(13.0), ios(16.0));` */ objc_enum_definition: $ => seq( 'typedef', field('type', $.objc_enum_specifier), repeat($.attribute_specifier), repeat($.objc_macro_invocation), ';', ), /** * `__typeof__(self)`, the backbone of the weak/strong dance: * * __weak typeof(self) weakSelf = self; * * C reaches this through `macro_type_specifier`, but C++ replaces * `type_specifier` wholesale and drops it, leaving `typeof` unusable in a * type position. Restored here as a sibling of C++'s own `decltype`. * * The operand is preferably an expression, so that `typeof(self)` marks * `self` as a variable; a genuine type (`typeof(NSString *)`) still works * through the second branch. */ objc_typeof_specifier: $ => seq( choice('typeof', '__typeof', '__typeof__'), '(', field('value', choice(prec.dynamic(1, $.expression), $.type_descriptor)), ')', ), /** * `instancetype` only ever appears as a method return type, so it is safe * to treat as a keyword. * * `id` and `Class` are *not* here, though clang treats them as keywords. * They are declared as plain typedefs in , and real code uses * both as ordinary variable names — wxWidgets writes `int id = ...; * if (id == wxID_HELP)` throughout its Cocoa backend. Making them keywords * broke every such file. They are ordinary identifiers here and are picked * out by the highlight queries by name, the same way `self`, `nil` and * `YES` are. */ objc_builtin_type: _ => 'instancetype', /** * Protocol qualifiers have no rule of their own. * * `id` and `NSObject` are both `template_type`, the * same shape C++ gives a template instantiation. Telling a protocol list * apart from Objective-C lightweight generics needs to know whether the * name is a generic class, which is a symbol table's job rather than a * parser's. Both hold type identifiers and highlight identically, so * nothing downstream notices. */ type_qualifier: (_, original) => choice( original, ...OBJC_TYPE_QUALIFIERS, ), /** * Admits an unexpanded macro where `override` or `final` would go, and * where an availability annotation trails a function declaration: * * virtual void SetLabel(const wxString&) wxOVERRIDE; * CG_EXTERN CFTypeID CGFunctionGetTypeID(void) API_AVAILABLE(macos(10.2)); * * Frameworks older than C++11 wrap `override` so they can compile either * way, and Apple annotates nearly every function this way. Neither macro * is reliably SCREAMING_SNAKE, but the position right after a parameter * list admits nothing else, so a bare identifier is safe to accept. * * Hooked here rather than on `virtual_specifier` itself, which is also * reachable after a class name and would make `class Foo {` ambiguous. */ _function_postfix: ($, original) => choice( original, prec.right(repeat1(seq( alias($.identifier, $.objc_macro_name), optional($.objc_macro_arguments), ))), ), /** * Allows a trailing macro before the semicolon: * * CG_EXTERN const CFStringRef kCGWindowNumber * API_AVAILABLE(macos(10.15.4), ios(13.4)); * * This declarator names a variable rather than a function, so * `_function_postfix` never sees it. Note the three-part version number, * which is not a C literal at all — another reason macro arguments are * kept as opaque text. */ declaration: $ => seq( $._declaration_specifiers, commaSep1(field('declarator', choice( seq( $._declarator, optional($.gnu_asm_expression), ), $.init_declarator, ))), repeat($.objc_macro_invocation), ';', ), /** * Lets `#if __has_include()` parse. * * C models a preprocessor call's arguments as preprocessor expressions, * and a bracketed header path is not one. `__has_include` is the only * common operator that takes one, but it guards the OpenGL and GLES * headers across the SDK. */ _preproc_expression: ($, original) => choice( original, $.system_lib_string, $.string_literal, ), /** * `class __exported IOFoo : public IOBar` * * A clang visibility attribute that sits between `class` and the name. It * is spelled in lowercase, so the macro rule cannot see it, and IOKit's * C++ headers put it on nearly every class they declare. */ _class_declaration: ($, original) => seq( optional('__exported'), original, ), /** * Kept as the inherited set. * * Admitting `objc_macro_invocation` here would catch a macro sitting * between the type and the name, as in * `CV_EXPORT const CFStringRef CV_NONNULL kCVBufferKey;`. It has been * tried twice and measured worse both times: it makes every macro call * ambiguous between "standalone item" and "modifier on what follows", * needs four extra conflicts to build at all, and then costs 0.5 points * on Apple's headers, 3.5 on application code and 10 on wxWidgets. A * leading macro already parses as a standalone item immediately before * the declaration, which is nearly as good and free. */ _declaration_modifiers: ($, original) => original, /** * An unexpanded preprocessor macro standing where a declaration would. * * Apple's headers are full of these — `NS_ASSUME_NONNULL_BEGIN` alone on a * line, `CG_EXTERN` before a function, `API_AVAILABLE(macos(10.9))` after * a method. A parser cannot expand them, and without this rule the first * one in a file is absorbed as a type name, swallowing the declaration * that follows and desynchronising everything after it. That costs the * rest of the file its highlighting, which is the whole point of the * exercise. * * The name is matched by shape rather than from a fixed list: uppercase * with at least one underscore. That covers every vendor prefix * (`NS_`, `CG_`, `API_`, `MTL_`, `SK_`) without enumerating them, while * leaving genuine all-caps types like `BOOL`, `SEL` and `IMP`, and * constants like `YES` and `NULL`, as ordinary identifiers. * * Deliberately unavailable inside expressions and statement bodies, so * that a macro constant such as `INT_MAX` in real code is never mistaken * for one of these. */ objc_macro_invocation: $ => prec.dynamic(-1, prec.right(seq( field('name', $.objc_macro_name), optional(field('arguments', $.objc_macro_arguments)), ))), /** * The arguments of an unexpanded macro, held as opaque text. * * They are not parsed as expressions because in general they are not * expressions. `NS_SWIFT_NAME(active(status:))` carries selector syntax, * and `RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(NSCoder *)c)` * carries a whole method declaration. Only the preprocessor can say what * the text means, so the grammar declines to guess and keeps it whole. * * Treating every macro call the same way also keeps the result * predictable: no macro's arguments are highlighted, rather than some * being highlighted and others not depending on whether they happened to * look like C. * * The text itself comes from the external scanner, which counts balanced * parentheses and so can find the closing one. An empty `MACRO()` yields * no text at all, hence the `optional`. */ objc_macro_arguments: $ => seq( '(', optional($.objc_macro_argument_text), ')', ), /** * Outranks `identifier` lexically, otherwise the tie is broken in favour * of a plain identifier and this rule never fires. Only reachable in the * positions listed above, so expressions are unaffected. * * The optional lowercase head allows a vendor prefix: wxWidgets writes * `wxBEGIN_EVENT_TABLE` and `wxCLANG_WARNING_RESTORE` where Apple writes * `NS_ASSUME_NONNULL_BEGIN`. The underscore-separated uppercase body is * what actually marks it as a macro. */ objc_macro_name: _ => token(prec(1, /[a-z]{0,3}[A-Z][A-Z0-9]*(_[A-Z0-9]+)+/)), /** * A trailing annotation macro spelled in lowercase, as clang's own are: * `- (void)reload __deprecated_msg("use x");` * * Arguments are required. Without them the name would be indistinguishable * from the next keyword of the selector itself, since both are bare * identifiers in the same position. */ objc_lowercase_macro: $ => prec.dynamic(-1, seq( field('name', alias($.identifier, $.objc_macro_name)), field('arguments', $.objc_macro_arguments), )), // --------------------------------------------------------------- // Block pointer declarators — `void (^name)(int)` // --------------------------------------------------------------- _declarator: ($, original) => choice(original, $.block_pointer_declarator), _field_declarator: ($, original) => choice( original, alias($.block_pointer_field_declarator, $.block_pointer_declarator), ), _type_declarator: ($, original) => choice( original, alias($.block_pointer_type_declarator, $.block_pointer_declarator), ), _abstract_declarator: ($, original) => choice( original, $.abstract_block_pointer_declarator, ), // `prec.dynamic` mirrors C's treatment of `pointer_declarator`: when a // named and an abstract reading both survive to the end, prefer the named. block_pointer_declarator: $ => prec.dynamic(1, prec.right(seq( '^', repeat($.type_qualifier), repeat($.objc_macro_invocation), field('declarator', optional($._declarator)), ))), block_pointer_field_declarator: $ => prec.right(seq( '^', repeat($.type_qualifier), repeat($.objc_macro_invocation), field('declarator', optional($._field_declarator)), )), block_pointer_type_declarator: $ => prec.right(seq( '^', repeat($.type_qualifier), repeat($.objc_macro_invocation), field('declarator', optional($._type_declarator)), )), /** * The abstract form carries macros too. An annotated block type with no * parameter name is how Apple writes most completion handlers: * `- (void)load:(void (^ NS_SWIFT_SENDABLE)(NSError *))handler;` */ abstract_block_pointer_declarator: $ => prec.right(seq( '^', repeat($.type_qualifier), repeat($.objc_macro_invocation), field('declarator', optional($._abstract_declarator)), )), // --------------------------------------------------------------- // @interface / @implementation / @protocol // --------------------------------------------------------------- objc_class_interface: $ => seq( // `__attribute__((objc_subclassing_restricted))` and friends may precede // the class, as Apple's own generated headers write them. repeat($.attribute_specifier), '@interface', field('name', $._type_identifier), optional(field('type_parameters', $.objc_type_parameter_list)), optional(field('category', $.objc_category)), optional(seq(':', field('superclass', $._type_identifier))), optional(field('protocols', $.objc_protocol_reference_list)), // A trailing availability macro (`@interface Foo : Bar API_AVAILABLE(...)`) // needs no clause of its own: `objc_macro_invocation` is already a body // item, so it is simply picked up as the first one. optional(field('ivars', $.objc_instance_variables)), repeat(field('body', $._objc_interface_item)), '@end', ), objc_class_implementation: $ => seq( '@implementation', field('name', $._type_identifier), optional(field('category', $.objc_category)), optional(seq(':', field('superclass', $._type_identifier))), optional(field('ivars', $.objc_instance_variables)), repeat(field('body', $._objc_implementation_item)), '@end', ), objc_protocol_declaration: $ => seq( '@protocol', field('name', $._type_identifier), optional(field('protocols', $.objc_protocol_reference_list)), repeat(field('body', $._objc_interface_item)), '@end', ), /** * `@interface Foo (Private)` and the empty class extension `@interface Foo ()`. * * Directly after a class name a parenthesised identifier is always a * category, never the parenthesised declarator or call expression that the * inherited C++ rules would otherwise see, hence the precedence. */ objc_category: $ => prec(PREC.CATEGORY, seq( '(', optional(field('name', $.identifier)), ')', )), /** * Lightweight generics on the declaration side: `@interface Box ...`. * * Immediately after a class name this outranks a protocol reference list, * which is the right call for modern code. The cost is that a root class * carrying only protocols and no superclass (`@interface Foo `, * essentially extinct) reads its protocols as type parameters. Both node * kinds hold type identifiers, so nothing downstream notices. */ objc_type_parameter_list: $ => prec.dynamic(1, seq( '<', commaSep1(field('parameter', $.objc_type_parameter)), '>', )), objc_type_parameter: $ => seq( optional(choice('__covariant', '__contravariant')), field('name', $._type_identifier), optional(seq(':', field('bound', $.type_descriptor))), ), objc_protocol_reference_list: $ => seq( '<', commaSep1(field('protocol', $._type_identifier)), '>', ), objc_instance_variables: $ => prec(PREC.IVARS, seq( '{', repeat(choice( $.objc_visibility_specifier, $._field_declaration_list_item, )), '}', )), objc_visibility_specifier: _ => choice( '@private', '@protected', '@public', '@package', ), _objc_interface_item: $ => choice( $.objc_method_declaration, $.objc_property_declaration, $.objc_availability_specifier, $.objc_macro_invocation, $.declaration, $.type_definition, $._empty_declaration, alias($.preproc_if_in_objc_interface, $.preproc_if), alias($.preproc_ifdef_in_objc_interface, $.preproc_ifdef), $.preproc_include, $.preproc_def, $.preproc_function_def, $.preproc_call, ), /** * Deliberately not `$._top_level_item`. C admits a bare compound statement * at top level, which would make the `{` after `@implementation Foo` * ambiguous with the ivar block. Listing the items that can really appear * in a class body removes the ambiguity at the source instead of papering * over it with a conflict. */ _objc_implementation_item: $ => choice( $.objc_method_definition, $.objc_property_synthesize, $.objc_property_dynamic, $.objc_property_declaration, $.objc_class_forward_declaration, $.objc_protocol_forward_declaration, $.objc_macro_invocation, $.function_definition, $.declaration, $.type_definition, $._empty_declaration, // A stray semicolon, as left behind by a macro that already supplies its // own: `RCT_EXPORT_MODULE();`. C++ allows one in a class body for the // same reason. Admitted only here, not in an interface body, where it // would be indistinguishable from `@protocol Foo;`. ';', alias($.preproc_if_in_objc_implementation, $.preproc_if), alias($.preproc_ifdef_in_objc_implementation, $.preproc_ifdef), $.preproc_include, $.preproc_def, $.preproc_function_def, $.preproc_call, ), objc_availability_specifier: _ => choice('@required', '@optional'), // --------------------------------------------------------------- // Methods // --------------------------------------------------------------- /** `- (void)doThing:(NSString *)a with:(int)b;` */ objc_method_declaration: $ => seq( field('scope', $.objc_method_scope), optional(field('return_type', $.objc_method_type)), field('selector', $.objc_method_selector), repeat($.attribute_specifier), repeat(choice($.objc_macro_invocation, $.objc_lowercase_macro)), ';', ), objc_method_definition: $ => seq( field('scope', $.objc_method_scope), optional(field('return_type', $.objc_method_type)), field('selector', $.objc_method_selector), repeat($.attribute_specifier), // An availability macro may sit between the selector and the body, just // as it may before the semicolon of a declaration. repeat($.objc_macro_invocation), optional(';'), field('body', $.compound_statement), ), /** `-` is an instance method, `+` a class method. */ objc_method_scope: _ => choice('-', '+'), /** * The parenthesised return or parameter type. * * Direction keywords sit in their own rule rather than in `type_qualifier` * so that they stay scoped to this position; `type_descriptor` already * accepts the ARC and nullability qualifiers on its own. */ objc_method_type: $ => seq( '(', repeat($.objc_method_type_qualifier), $.type_descriptor, ')', ), objc_method_type_qualifier: _ => choice(...OBJC_METHOD_TYPE_QUALIFIERS), /** * Either a bare selector (`init`) or one or more keyword/argument pairs * (`initWithFrame:` `style:`). Trailing `, ...` marks a variadic method. */ /** * Right-associative so that the parameter list keeps absorbing keywords. * A trailing lowercase annotation macro starts with a bare identifier in * the same position as the next selector keyword, and only the token after * it tells them apart. */ objc_method_selector: $ => prec.right(choice( field('name', $._objc_selector_identifier), seq( repeat1(field('parameter', $.objc_method_parameter)), optional(seq(',', '...')), ), )), objc_method_parameter: $ => seq( optional(field('keyword', $._objc_selector_identifier)), ':', optional(field('type', $.objc_method_type)), repeat($.attribute_specifier), field('name', $.identifier), ), /** * Selector pieces are not restricted to C identifiers: `- (void)in:`, * `- (id)copy`, `- (void)default:` are all legal. Admit the C and * Objective-C keywords that show up in real frameworks. */ _objc_selector_identifier: $ => choice( $.identifier, alias(choice( 'in', 'out', 'inout', 'copy', 'class', 'default', 'new', 'delete', 'instancetype', 'static', 'return', 'for', 'while', 'do', 'switch', 'case', 'break', 'continue', 'if', 'else', 'signed', 'unsigned', 'const', 'volatile', 'auto', 'register', 'extern', 'struct', 'union', 'enum', 'typedef', 'sizeof', 'this', 'template', 'operator', 'namespace', 'using', 'try', 'catch', 'throw', 'public', 'private', 'protected', 'virtual', 'inline', 'explicit', 'friend', ), $.identifier), ), // --------------------------------------------------------------- // Properties // --------------------------------------------------------------- /** `@property (nonatomic, copy, nullable) NSString *name;` */ objc_property_declaration: $ => seq( '@property', optional(field('attributes', $.objc_property_attribute_list)), $._declaration_specifiers, commaSep1(field('declarator', $._declarator)), repeat($.attribute_specifier), repeat($.objc_macro_invocation), ';', ), objc_property_attribute_list: $ => seq( '(', commaSep(field('attribute', $.objc_property_attribute)), ')', ), /** * Plain attributes, the two that take a value (`getter=isDone`, * `setter=setDone:`), and an unexpanded macro. Apple ships the last of * these in its own headers: `@property(readonly, NS_NONATOMIC_IOSONLY)`. */ objc_property_attribute: $ => choice( ...PROPERTY_ATTRIBUTES, $.objc_macro_invocation, seq( field('name', choice('getter', 'setter')), '=', field('value', $._objc_selector_identifier), optional(':'), ), ), /** `@synthesize name = _name, other;` */ objc_property_synthesize: $ => seq( '@synthesize', commaSep1(seq( field('property', $.identifier), optional(seq('=', field('ivar', $.identifier))), )), ';', ), objc_property_dynamic: $ => seq( '@dynamic', commaSep1(field('property', $.identifier)), ';', ), // --------------------------------------------------------------- // Forward declarations and module imports // --------------------------------------------------------------- objc_class_forward_declaration: $ => seq( '@class', commaSep1(seq( field('name', $._type_identifier), optional(field('type_parameters', $.objc_type_parameter_list)), )), ';', ), objc_protocol_forward_declaration: $ => seq( '@protocol', commaSep1(field('name', $._type_identifier)), ';', ), objc_compatibility_alias: $ => seq( '@compatibility_alias', field('alias', $._type_identifier), field('name', $._type_identifier), ';', ), /** `@import Foundation;` and `@import Foundation.NSString;` */ objc_module_import: $ => seq( '@import', field('module', sep1($.identifier, '.')), ';', ), // --------------------------------------------------------------- // Message expressions // --------------------------------------------------------------- /** * C++ attributes, respelled with two separate bracket tokens. * * C lexes `[[` as a single token, which is fatal here: a nested message * send also opens with two brackets, so every * `[[NSRunLoop mainRunLoop] runMode:...]` in the file would be lexed as * the start of an attribute and lost. Splitting the token lets the parser * decide between the two readings instead of the lexer. * * This is not free. Two C++ constructs that open with a single bracket * become collateral damage, because a lone `[` can now begin an attribute * and the parser commits to that reading before the next token could rule * it out: * * - C++17 structured bindings, `auto [a, b] = pair;` * - lambda init-captures, `[&a = x_, &b = y()](auto fn) { ... }` * * Raising the declarator's precedence, lowering the attribute's, and * declaring the conflict at four different levels were each tried and none * moved it. The two conflicts above are load-bearing: the grammar will not * build without them, and they are what pulls capture lists into the * expression reading. * * The trade was settled by counting rather than by taste. Across the test * corpora, nested message sends appear in 586 files; structured bindings * in 2 and init-captures in 3. Objective-C++ that never sends a nested * message is not Objective-C++. * * `[ [nodiscard] ]` with spaces is also accepted now, which C++ does not * allow. That one is harmless. */ attribute_declaration: $ => seq( '[', '[', commaSep1($.attribute), ']', ']', ), /** * `[receiver selector:arg otherKeyword:arg]` * * Shares its opening bracket with subscripts, lambda captures and C++ * attributes; the declared conflicts above let the parser explore them. * The dynamic precedence settles the leftovers in favour of a message * send, which in Objective-C source is overwhelmingly the right guess. */ message_expression: $ => prec.dynamic(1, prec(PREC.MESSAGE, seq( '[', field('receiver', $._objc_receiver), field('selector', $.objc_message_selector), ']', ))), _objc_receiver: $ => choice( $.expression, $.objc_super, $.objc_builtin_type, ), objc_message_selector: $ => choice( field('name', $._objc_selector_identifier), seq( repeat1(field('argument', $.objc_message_argument)), // Variadic tail: `[NSString stringWithFormat:@"%@ %@", a, b]` repeat(seq(',', field('variadic_argument', $.expression))), ), ), objc_message_argument: $ => seq( optional(field('keyword', $._objc_selector_identifier)), ':', field('value', $.expression), ), /** * `super` is a receiver-only keyword. `self`, by contrast, is a real * implicit parameter and is assignable (`self = [super init]`), so it is * left as an ordinary identifier and picked out by the highlight queries. */ objc_super: _ => 'super', // --------------------------------------------------------------- // Blocks // --------------------------------------------------------------- /** * `^{ ... }`, `^(NSError *e) { ... }`, `^BOOL(id a, id b) { ... }` * * Ranks above the inherited binary `^` so a statement-leading caret is * read as a block rather than as a dangling xor. */ block_expression: $ => prec(PREC.BLOCK, seq( '^', optional(field('return_type', $.objc_block_return_type)), optional(field('parameters', $.parameter_list)), repeat($.attribute_specifier), field('body', $.compound_statement), )), /** * The return type of a block literal, spelled out rather than reusing * `type_descriptor`. A full descriptor would happily read the parameter * list as part of the type, turning `^BOOL(id a)` into a block returning * a function. Only a type and its pointers belong here. */ objc_block_return_type: $ => seq( repeat($.type_qualifier), field('type', $.type_specifier), repeat($.type_qualifier), repeat(seq('*', repeat($.type_qualifier))), ), // --------------------------------------------------------------- // @-expressions // --------------------------------------------------------------- /** `@selector(tableView:didSelectRowAtIndexPath:)` */ objc_selector_expression: $ => seq( '@selector', '(', field('selector', $.objc_selector_name), ')', ), objc_selector_name: $ => choice( $._objc_selector_identifier, repeat1(seq(optional($._objc_selector_identifier), ':')), ), objc_protocol_expression: $ => seq( '@protocol', '(', field('name', $._type_identifier), ')', ), objc_encode_expression: $ => seq( '@encode', '(', field('type', $.type_descriptor), ')', ), /** `@available(iOS 13.0, macOS 10.15, *)` */ objc_available_expression: $ => seq( choice('@available', '__builtin_available'), '(', commaSep1(field('platform', choice($.objc_platform_version, '*'))), ')', ), objc_platform_version: $ => seq( field('platform', $.identifier), field('version', $.number_literal), ), // --------------------------------------------------------------- // Boxed literals // --------------------------------------------------------------- /** `@"text"`, and the adjacent-concatenation form `@"a" @"b"`. */ /** * `@"text"`, and its concatenations. * * Adjacent pieces may repeat the `@` or leave it off, and real code mixes * both freely: * * @"The loaders %@ and %@ both reported that" * " they can load the URL %@, and have equal priority" * * Only the first piece needs the sigil, so everything after it is an * ordinary C string. */ objc_string_literal: $ => prec.right(seq( '@', field('value', choice($.string_literal, $.raw_string_literal)), repeat(choice( seq('@', field('value', choice($.string_literal, $.raw_string_literal))), field('value', choice($.string_literal, $.raw_string_literal)), )), )), /** `@42`, `@YES`, `@'c'`, `@(expr)` */ objc_boxed_expression: $ => seq( '@', field('value', choice( $.number_literal, $.char_literal, $.true, $.false, $.identifier, seq('(', $.expression, ')'), )), ), /** `@[a, b, c]` */ objc_array_literal: $ => seq( '@', '[', commaSep(field('element', $.expression)), optional(','), ']', ), /** `@{key: value, ...}` */ objc_dictionary_literal: $ => seq( '@', '{', commaSep(field('entry', $.objc_dictionary_entry)), optional(','), '}', ), objc_dictionary_entry: $ => seq( field('key', $.expression), ':', field('value', $.expression), ), // --------------------------------------------------------------- // Statements // --------------------------------------------------------------- objc_autoreleasepool_statement: $ => seq( '@autoreleasepool', field('body', $.compound_statement), ), objc_synchronized_statement: $ => seq( '@synchronized', '(', field('object', $.expression), ')', field('body', $.compound_statement), ), objc_try_statement: $ => seq( '@try', field('body', $.compound_statement), repeat(field('handler', $.objc_catch_clause)), optional(field('finalizer', $.objc_finally_clause)), ), objc_catch_clause: $ => seq( '@catch', '(', field('parameter', choice($.parameter_declaration, '...')), ')', field('body', $.compound_statement), ), objc_finally_clause: $ => seq( '@finally', field('body', $.compound_statement), ), objc_throw_statement: $ => seq( '@throw', optional(field('value', $.expression)), ';', ), /** Fast enumeration: `for (id item in collection) { ... }` */ objc_for_in_statement: $ => prec(1, seq( 'for', '(', field('initializer', choice( seq($._declaration_specifiers, field('declarator', $._declarator)), $.expression, )), 'in', field('collection', $.expression), ')', field('body', $.statement), )), }, }); /** * Zero or more `rule`, comma separated. * @param {RuleOrLiteral} rule */ function commaSep(rule) { return optional(commaSep1(rule)); } /** * One or more `rule`, comma separated. * @param {RuleOrLiteral} rule */ function commaSep1(rule) { return sep1(rule, ','); } /** * One or more `rule`, separated by `separator`. * @param {RuleOrLiteral} rule * @param {RuleOrLiteral} separator */ function sep1(rule, separator) { return seq(rule, repeat(seq(separator, rule))); }