tree-sitter-swift
Version:
A tree-sitter grammar for the Swift programming language.
1,511 lines (1,493 loc) • 69.5 kB
JavaScript
"use strict";
/*
* MIT License
*
* Copyright (c) 2021 alex-pinkus
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
const PRECS = {
multiplication: 11,
addition: 10,
infix_operations: 9,
nil_coalescing: 8,
check: 7,
prefix_operations: 7,
comparison: 6,
postfix_operations: 6,
equality: 5,
conjunction: 4,
disjunction: 3,
block: 2,
loop: 1,
keypath: 1,
parameter_pack: 1,
control_transfer: 0,
as: -1,
tuple: -1,
if: -1,
switch: -1,
do: -1,
fully_open_range: -1,
range: -1,
navigation: -1,
expr: -1,
ty: -1,
call: -2,
ternary: -2,
try: -2,
call_suffix: -2,
range_suffix: -2,
ternary_binary_suffix: -2,
await: -2,
assignment: -3,
comment: -3,
lambda: -3,
regex: -4,
};
const DYNAMIC_PRECS = {
call: 1,
};
const DEC_DIGITS = token(sep1(/[0-9]+/, /_+/));
const HEX_DIGITS = token(sep1(/[0-9a-fA-F]+/, /_+/));
const OCT_DIGITS = token(sep1(/[0-7]+/, /_+/));
const BIN_DIGITS = token(sep1(/[01]+/, /_+/));
const REAL_EXPONENT = token(seq(/[eE]/, optional(/[+-]/), DEC_DIGITS));
const HEX_REAL_EXPONENT = token(seq(/[pP]/, optional(/[+-]/), DEC_DIGITS));
var LEXICAL_IDENTIFIER;
if (tree_sitter_version_supports_emoji()) {
LEXICAL_IDENTIFIER =
/[_\p{XID_Start}\p{Emoji}&&[^0-9#*]](\p{EMod}|\x{FE0F}\x{20E3}?)?([_\p{XID_Continue}\p{Emoji}\x{200D}](\p{EMod}|\x{FE0F}\x{20E3}?)?)*/;
} else {
LEXICAL_IDENTIFIER = /[_\p{XID_Start}][_\p{XID_Continue}]*/;
}
module.exports = grammar({
name: "swift",
conflicts: ($) => [
// @Type(... could either be an annotation constructor invocation or an annotated expression
[$.attribute],
[$._attribute_argument],
// Is `foo { ... }` a constructor invocation or function invocation?
[$._simple_user_type, $._expression],
// To support nested types A.B not being interpreted as `(navigation_expression ... (type_identifier)) (navigation_suffix)`
[$.user_type],
// How to tell the difference between Foo.bar(with:and:), and Foo.bar(with: smth, and: other)? You need GLR
[$.value_argument],
// { (foo, bar) ...
[$._expression, $.lambda_parameter],
[$._primary_expression, $.lambda_parameter],
// (start: start, end: end)
[$._tuple_type_item_identifier, $.tuple_expression],
// After a `{` in a function or switch context, it's ambigous whether we're starting a set of local statements or
// applying some modifiers to a capture or pattern.
[$.modifiers],
// `+(...)` is ambigously either "call the function produced by a reference to the operator `+`" or "use the unary
// operator `+` on the result of the parenthetical expression."
[$._additive_operator, $._prefix_unary_operator],
[$._referenceable_operator, $._prefix_unary_operator],
// `{ [self, b, c] ...` could be a capture list or an array literal depending on what else happens.
[$.capture_list_item, $._expression],
[$.capture_list_item, $._expression, $._simple_user_type],
[$._primary_expression, $.capture_list_item],
// a ? b : c () could be calling c(), or it could be calling a function that's produced by the result of
// `(a ? b : c)`. We have a small hack to force it to be the former of these by intentionally introducing a
// conflict.
[$.call_suffix, $.expr_hack_at_ternary_binary_call_suffix],
// try {expression} is a bit magic and applies quite broadly: `try foo()` and `try foo { }` show that this is right
// associative, and `try foo ? bar() : baz` even more so. But it doesn't always win: something like
// `if try foo { } ...` should award its braces to the `if`. In order to make this actually happen, we need to parse
// all the options and pick the best one that doesn't error out.
[$.try_expression, $._unary_expression],
[$.try_expression, $._expression],
// await {expression} has the same special cases as `try`.
[$.await_expression, $._unary_expression],
[$.await_expression, $._expression],
// In a computed property, when you see an @attribute, it's not yet clear if that's going to be for a
// locally-declared class or a getter / setter specifier.
[
$._local_property_declaration,
$._local_typealias_declaration,
$._local_function_declaration,
$._local_class_declaration,
$.computed_getter,
$.computed_modify,
$.computed_setter,
],
// The `class` modifier is legal in many of the same positions that a class declaration itself would be.
[$._bodyless_function_declaration, $.property_modifier],
[$.init_declaration, $.property_modifier],
// Patterns, man
[$._navigable_type_expression, $._case_pattern],
[$._no_expr_pattern_already_bound, $._binding_pattern_no_expr],
// On encountering a closure starting with `{ @Foo ...`, we don't yet know if that attribute applies to the closure
// type or to a declaration within the closure. What a mess! We just have to hope that if we keep going, only one of
// those will parse (because there will be an `in` or a `let`).
[
$._lambda_type_declaration,
$._local_property_declaration,
$._local_typealias_declaration,
$._local_function_declaration,
$._local_class_declaration,
],
// We want `foo() { }` to be treated as one function call, but we _also_ want `if foo() { ... }` to be treated as a
// full if-statement. This means we have to treat it as a conflict rather than purely a left or right associative
// construct, and let the parser realize that the second expression won't parse properly with the `{ ... }` as a
// lambda.
[$.constructor_suffix],
[$.call_suffix],
// `actor` is allowed to be an identifier, even though it is also a locally permitted declaration. If we encounter
// it, the only way to know what it's meant to be is to keep going.
[$._modifierless_class_declaration, $.property_modifier],
[$._fn_call_lambda_arguments],
// `borrowing` and `consuming` are legal as identifiers, but are also legal modifiers
[$.parameter_modifiers],
// These are keywords sometimes, but simple identifiers other times, and it just depends on the rest of their usage.
[$._contextual_simple_identifier, $._modifierless_class_declaration],
[$._contextual_simple_identifier, $.property_behavior_modifier],
[$._contextual_simple_identifier, $.parameter_modifier],
[$._contextual_simple_identifier, $.type_parameter_pack],
[$._contextual_simple_identifier, $.type_pack_expansion],
[$._contextual_simple_identifier, $.visibility_modifier],
],
extras: ($) => [
$.comment,
$.multiline_comment,
/\s+/, // Whitespace
],
externals: ($) => [
// Comments and raw strings are parsed in a custom scanner because they require us to carry forward state to
// maintain symmetry. For instance, parsing a multiline comment requires us to increment a counter whenever we see
// `/*`, and decrement it whenever we see `*/`. A standard grammar would only be able to exit the comment at the
// first `*/` (like C does). Similarly, when you start a string with `##"`, you're required to include the same
// number of `#` symbols to end it.
$.multiline_comment,
$.raw_str_part,
$.raw_str_continuing_indicator,
$.raw_str_end_part,
// Because Swift doesn't have explicit semicolons, we also do some whitespace handling in a custom scanner. Line
// breaks are _sometimes_ meaningful as the end of a statement: try to write `let foo: Foo let bar: Bar`, for
// instance and the compiler will complain, but add either a newline or a semicolon and it's fine. We borrow the
// idea from the Kotlin grammar that a newline is sometimes a "semicolon". By including `\n` in both `_semi` and
// an anonymous `whitespace` extras, we _should_ be able to let the parser decide if a newline is meaningful. If the
// parser sees something like `foo.bar(1\n)`, it knows that a "semicolon" would not be valid there, so it parses
// that as whitespace. On the other hand, `let foo: Foo\n let bar: Bar` has a meaningful newline.
// Unfortunately, we can't simply stop at that. There are some expressions and statements that remain valid if you
// end them early, but are expected to be parsed across multiple lines. One particular nefarious example is a
// function declaration, where you might have something like `func foo<A>(args: A) -> Foo throws where A: Hashable`.
// This would still be a valid declaration even if it ended after the `)`, the `Foo`, or the `throws`, so a grammar
// that simply interprets a newline as "sometimes a semi" would parse those incorrectly.
// To solve that case, our custom scanner must do a bit of extra lookahead itself. If we're about to generate a
// `_semi`, we advance a bit further to see if the next non-whitespace token would be one of these other operators.
// If so, we ignore the `_semi` and just produce the operator; if not, we produce the `_semi` and let the rest of
// the grammar sort it out. This isn't perfect, but it works well enough most of the time.
$._implicit_semi,
$._explicit_semi,
// Every one of the below operators will suppress a `_semi` if we encounter it after a newline.
$._arrow_operator_custom,
$._dot_custom,
$._conjunction_operator_custom,
$._disjunction_operator_custom,
$._nil_coalescing_operator_custom,
$._eq_custom,
$._eq_eq_custom,
$._plus_then_ws,
$._minus_then_ws,
$._bang_custom,
$._throws_keyword,
$._rethrows_keyword,
$.default_keyword,
$.where_keyword,
$["else"],
$.catch_keyword,
$._as_custom,
$._as_quest_custom,
$._as_bang_custom,
$._async_keyword_custom,
$._custom_operator,
$._hash_symbol_custom,
$._directive_if,
$._directive_elseif,
$._directive_else,
$._directive_endif,
// Fake operator that will never get triggered, but follows the sequence of characters for `try!`. Tracked by the
// custom scanner so that it can avoid triggering `$.bang` for that case.
$._fake_try_bang,
],
inline: ($) => [$._locally_permitted_modifiers],
rules: {
////////////////////////////////
// File Structure
////////////////////////////////
source_file: ($) =>
seq(
optional($.shebang_line),
optional(
seq(
$._top_level_statement,
repeat(seq($._semi, $._top_level_statement)),
optional($._semi)
)
)
),
_semi: ($) => choice($._implicit_semi, $._explicit_semi),
shebang_line: ($) => seq($._hash_symbol, "!", /[^\r\n]*/),
////////////////////////////////
// Lexical Structure - https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html
////////////////////////////////
comment: ($) => token(prec(PRECS.comment, seq("//", /.*/))),
// Identifiers
simple_identifier: ($) =>
choice(
LEXICAL_IDENTIFIER,
/`[^\r\n` ]*`/,
/\$[0-9]+/,
token(seq("$", LEXICAL_IDENTIFIER)),
$._contextual_simple_identifier
),
// Keywords that were added after they were already legal as identifiers. `tree-sitter` will prefer exact matches
// when parsing so unless we explicitly say that these are legal, the parser will interpret them as their keyword.
_contextual_simple_identifier: ($) =>
choice(
"actor",
"async",
"each",
"lazy",
"repeat",
"package",
$._parameter_ownership_modifier
),
identifier: ($) => sep1($.simple_identifier, $._dot),
// Literals
_basic_literal: ($) =>
choice(
$.integer_literal,
$.hex_literal,
$.oct_literal,
$.bin_literal,
$.real_literal,
$.boolean_literal,
$._string_literal,
$.regex_literal,
"nil"
),
real_literal: ($) =>
token(
choice(
seq(DEC_DIGITS, REAL_EXPONENT),
seq(optional(DEC_DIGITS), ".", DEC_DIGITS, optional(REAL_EXPONENT)),
seq(
"0x",
HEX_DIGITS,
optional(seq(".", HEX_DIGITS)),
HEX_REAL_EXPONENT
)
)
),
integer_literal: ($) => token(seq(optional(/[1-9]/), DEC_DIGITS)),
hex_literal: ($) => token(seq("0", /[xX]/, HEX_DIGITS)),
oct_literal: ($) => token(seq("0", /[oO]/, OCT_DIGITS)),
bin_literal: ($) => token(seq("0", /[bB]/, BIN_DIGITS)),
boolean_literal: ($) => choice("true", "false"),
// String literals
_string_literal: ($) =>
choice(
$.line_string_literal,
$.multi_line_string_literal,
$.raw_string_literal
),
line_string_literal: ($) =>
seq(
'"',
repeat(choice(field("text", $._line_string_content), $._interpolation)),
'"'
),
_line_string_content: ($) => choice($.line_str_text, $.str_escaped_char),
line_str_text: ($) => /[^\\"]+/,
str_escaped_char: ($) =>
choice($._escaped_identifier, $._uni_character_literal),
_uni_character_literal: ($) => seq("\\", "u", /\{[0-9a-fA-F]+\}/),
multi_line_string_literal: ($) =>
seq(
'"""',
repeat(
choice(field("text", $._multi_line_string_content), $._interpolation)
),
'"""'
),
raw_string_literal: ($) =>
seq(
repeat(
seq(
field("text", $.raw_str_part),
field("interpolation", $.raw_str_interpolation),
optional($.raw_str_continuing_indicator)
)
),
field("text", $.raw_str_end_part)
),
raw_str_interpolation: ($) =>
seq($.raw_str_interpolation_start, $._interpolation_contents, ")"),
raw_str_interpolation_start: ($) => /\\#*\(/,
_multi_line_string_content: ($) =>
choice($.multi_line_str_text, $.str_escaped_char, '"'),
_interpolation: ($) => seq("\\(", $._interpolation_contents, ")"),
_interpolation_contents: ($) =>
sep1Opt(
field(
"interpolation",
alias($.value_argument, $.interpolated_expression)
),
","
),
_escaped_identifier: ($) => /\\[0\\tnr"'\n]/,
multi_line_str_text: ($) => /[^\\"]+/,
// Based on https://gitlab.com/woolsweater/tree-sitter-swifter/-/blob/3d47c85bd47ce54cdf2023a9c0e01eb90adfcc1d/grammar.js#L1019
// But required modifications to hit all of the cases in SE-354
regex_literal: ($) =>
choice(
$._extended_regex_literal,
$._multiline_regex_literal,
$._oneline_regex_literal
),
_extended_regex_literal: ($) =>
seq($._hash_symbol, /\/((\/[^#])|[^\n])+\/#/),
_multiline_regex_literal: ($) =>
seq($._hash_symbol, /\/\n/, /(\/[^#]|[^/])*?\n\/#/),
_oneline_regex_literal: ($) =>
token(
prec(
PRECS.regex,
seq(
"/",
token.immediate(/[^ \t\n]?[^/\n]*[^ \t\n/]/),
token.immediate("/")
)
)
),
////////////////////////////////
// Types - https://docs.swift.org/swift-book/ReferenceManual/Types.html
////////////////////////////////
type_annotation: ($) =>
seq(":", field("type", $._possibly_implicitly_unwrapped_type)),
_possibly_implicitly_unwrapped_type: ($) =>
seq($._type, optional(token.immediate("!"))),
_type: ($) =>
prec.right(
PRECS.ty,
seq(optional($.type_modifiers), field("name", $._unannotated_type))
),
_unannotated_type: ($) =>
prec.right(
PRECS.ty,
choice(
$.user_type,
$.tuple_type,
$.function_type,
$.array_type,
$.dictionary_type,
$.optional_type,
$.metatype,
$.opaque_type,
$.existential_type,
$.protocol_composition_type,
$.type_parameter_pack,
$.type_pack_expansion,
$.suppressed_constraint
)
),
// The grammar just calls this whole thing a `type-identifier` but that's a bit confusing.
user_type: ($) => sep1($._simple_user_type, $._dot),
_simple_user_type: ($) =>
prec.right(
PRECS.ty,
seq(
alias($.simple_identifier, $.type_identifier),
optional($.type_arguments)
)
),
tuple_type: ($) =>
choice(
seq(
"(",
optional(sep1Opt(field("element", $.tuple_type_item), ",")),
")"
),
alias($._parenthesized_type, $.tuple_type_item)
),
tuple_type_item: ($) =>
prec(
PRECS.expr,
seq(
optional($._tuple_type_item_identifier),
optional($.parameter_modifiers),
field("type", $._type)
)
),
_tuple_type_item_identifier: ($) =>
prec(
PRECS.expr,
seq(
optional($.wildcard_pattern),
field("name", $.simple_identifier),
":"
)
),
function_type: ($) =>
seq(
field("params", choice($.tuple_type, $._unannotated_type)),
optional($._async_keyword),
optional($.throws),
$._arrow_operator,
field("return_type", $._type)
),
array_type: ($) => seq("[", field("element", $._type), "]"),
dictionary_type: ($) =>
seq("[", field("key", $._type), ":", field("value", $._type), "]"),
optional_type: ($) =>
prec.left(
seq(
field(
"wrapped",
choice($.user_type, $.tuple_type, $.array_type, $.dictionary_type)
),
repeat1(alias($._immediate_quest, "?"))
)
),
metatype: ($) => seq($._unannotated_type, ".", choice("Type", "Protocol")),
_quest: ($) => "?",
_immediate_quest: ($) => token.immediate("?"),
opaque_type: ($) => prec.right(seq("some", $._unannotated_type)),
existential_type: ($) => prec.right(seq("any", $._unannotated_type)),
type_parameter_pack: ($) => prec.left(seq("each", $._unannotated_type)),
type_pack_expansion: ($) => prec.left(seq("repeat", $._unannotated_type)),
protocol_composition_type: ($) =>
prec.left(
seq(
$._unannotated_type,
repeat1(seq("&", prec.right($._unannotated_type)))
)
),
suppressed_constraint: ($) =>
prec.right(
seq(
"~",
field("suppressed", alias($.simple_identifier, $.type_identifier))
)
),
////////////////////////////////
// Expressions - https://docs.swift.org/swift-book/ReferenceManual/Expressions.html
////////////////////////////////
_expression: ($) =>
prec(
PRECS.expr,
choice(
$.simple_identifier,
$._unary_expression,
$._binary_expression,
$.ternary_expression,
$._primary_expression,
$.if_statement,
$.switch_statement,
$.assignment,
$.value_parameter_pack,
$.value_pack_expansion,
seq($._expression, alias($._immediate_quest, "?"))
)
),
// Unary expressions
_unary_expression: ($) =>
choice(
$.postfix_expression,
$.call_expression,
$.macro_invocation,
$.constructor_expression,
$.navigation_expression,
$.prefix_expression,
$.as_expression,
$.selector_expression,
$.open_start_range_expression,
$.open_end_range_expression,
$.directive,
$.diagnostic
),
postfix_expression: ($) =>
prec.left(
PRECS.postfix_operations,
seq(
field("target", $._expression),
field("operation", $._postfix_unary_operator)
)
),
constructor_expression: ($) =>
prec(
PRECS.call,
seq(
field(
"constructed_type",
choice($.array_type, $.dictionary_type, $.user_type)
),
$.constructor_suffix
)
),
_parenthesized_type: ($) =>
seq(
"(",
field(
"element",
choice($.opaque_type, $.existential_type, $.dictionary_type)
),
")"
),
navigation_expression: ($) =>
prec.left(
PRECS.navigation,
seq(
field(
"target",
choice(
$._navigable_type_expression,
$._expression,
$._parenthesized_type
)
),
field("suffix", $.navigation_suffix)
)
),
_navigable_type_expression: ($) =>
choice($.user_type, $.array_type, $.dictionary_type),
open_start_range_expression: ($) =>
prec.right(
PRECS.range,
seq(
$._range_operator,
prec.right(PRECS.range_suffix, field("end", $._expression))
)
),
_range_operator: ($) =>
choice($._open_ended_range_operator, $._three_dot_operator),
open_end_range_expression: ($) =>
prec.right(
PRECS.range,
seq(field("start", $._expression), $._three_dot_operator)
),
prefix_expression: ($) =>
prec.left(
PRECS.prefix_operations,
seq(
field("operation", $._prefix_unary_operator),
field(
"target",
choice(
$._expression,
alias(choice("async", "if", "switch"), $._expression)
)
)
)
),
as_expression: ($) =>
prec.left(
PRECS.as,
seq(field("expr", $._expression), $.as_operator, field("type", $._type))
),
selector_expression: ($) =>
seq(
$._hash_symbol,
"selector",
"(",
optional(choice("getter:", "setter:")),
$._expression,
")"
),
// Binary expressions
_binary_expression: ($) =>
choice(
$.multiplicative_expression,
$.additive_expression,
$.range_expression,
$.infix_expression,
$.nil_coalescing_expression,
$.check_expression,
$.equality_expression,
$.comparison_expression,
$.conjunction_expression,
$.disjunction_expression,
$.bitwise_operation
),
multiplicative_expression: ($) =>
prec.left(
PRECS.multiplication,
seq(
field("lhs", $._expression),
field("op", $._multiplicative_operator),
field("rhs", $._expression)
)
),
additive_expression: ($) =>
prec.left(
PRECS.addition,
seq(
field("lhs", $._expression),
field("op", $._additive_operator),
field("rhs", $._expression)
)
),
range_expression: ($) =>
prec.right(
PRECS.range,
seq(
field("start", $._expression),
field("op", $._range_operator),
field("end", $._expr_hack_at_ternary_binary_suffix)
)
),
infix_expression: ($) =>
prec.left(
PRECS.infix_operations,
seq(
field("lhs", $._expression),
field("op", $.custom_operator),
field("rhs", $._expr_hack_at_ternary_binary_suffix)
)
),
nil_coalescing_expression: ($) =>
prec.right(
PRECS.nil_coalescing,
seq(
field("value", $._expression),
$._nil_coalescing_operator,
field("if_nil", $._expr_hack_at_ternary_binary_suffix)
)
),
check_expression: ($) =>
prec.left(
PRECS.check,
seq(
field("target", $._expression),
field("op", $._is_operator),
field("type", $._type)
)
),
comparison_expression: ($) =>
prec.left(
seq(
field("lhs", $._expression),
field("op", $._comparison_operator),
field("rhs", $._expr_hack_at_ternary_binary_suffix)
)
),
equality_expression: ($) =>
prec.left(
PRECS.equality,
seq(
field("lhs", $._expression),
field("op", $._equality_operator),
field("rhs", $._expr_hack_at_ternary_binary_suffix)
)
),
conjunction_expression: ($) =>
prec.left(
PRECS.conjunction,
seq(
field("lhs", $._expression),
field("op", $._conjunction_operator),
field("rhs", $._expr_hack_at_ternary_binary_suffix)
)
),
disjunction_expression: ($) =>
prec.left(
PRECS.disjunction,
seq(
field("lhs", $._expression),
field("op", $._disjunction_operator),
field("rhs", $._expr_hack_at_ternary_binary_suffix)
)
),
bitwise_operation: ($) =>
prec.left(
seq(
field("lhs", $._expression),
field("op", $._bitwise_binary_operator),
field("rhs", $._expr_hack_at_ternary_binary_suffix)
)
),
custom_operator: ($) => choice(token(/[\/]+[*]+/), $._custom_operator),
// Suffixes
navigation_suffix: ($) =>
seq(
$._dot,
field("suffix", choice($.simple_identifier, $.integer_literal))
),
call_suffix: ($) =>
prec(
PRECS.call_suffix,
choice(
$.value_arguments,
prec.dynamic(-1, $._fn_call_lambda_arguments), // Prefer to treat `foo() { }` as one call not two
seq($.value_arguments, $._fn_call_lambda_arguments)
)
),
constructor_suffix: ($) =>
prec(
PRECS.call_suffix,
choice(
alias($._constructor_value_arguments, $.value_arguments),
prec.dynamic(-1, $._fn_call_lambda_arguments), // As above
seq(
alias($._constructor_value_arguments, $.value_arguments),
$._fn_call_lambda_arguments
)
)
),
_constructor_value_arguments: ($) =>
seq("(", optional(sep1Opt($.value_argument, ",")), ")"),
_fn_call_lambda_arguments: ($) =>
sep1($.lambda_literal, seq(field("name", $.simple_identifier), ":")),
type_arguments: ($) => prec.left(seq("<", sep1Opt($._type, ","), ">")),
value_arguments: ($) =>
seq(
choice(
seq("(", optional(sep1Opt($.value_argument, ",")), ")"),
seq("[", optional(sep1Opt($.value_argument, ",")), "]")
)
),
value_argument_label: ($) =>
prec.left(
choice(
$.simple_identifier,
// We don't rely on $._contextual_simple_identifier here because
// these don't usually fall into that category.
alias("if", $.simple_identifier),
alias("switch", $.simple_identifier)
)
),
value_argument: ($) =>
prec.left(
seq(
optional($.type_modifiers),
choice(
repeat1(
seq(field("reference_specifier", $.value_argument_label), ":")
),
seq(
optional(seq(field("name", $.value_argument_label), ":")),
field("value", $._expression)
)
)
)
),
try_expression: ($) =>
prec.right(
PRECS["try"],
seq(
$.try_operator,
field(
"expr",
choice(
// Prefer direct calls, e.g. `try foo()`, over indirect like `try a ? b() : c`. This allows us to have
// left associativity for the direct calls, which is technically wrong but is the only way to resolve the
// ambiguity of `if foo { ... }` in the correct direction.
prec.right(-2, $._expression),
prec.left(0, $._binary_expression),
prec.left(0, $.call_expression),
// Similarly special case the ternary expression, where `try` may come earlier than it is actually needed.
// When the parser just encounters some identifier after a `try`, it should prefer the `call_expression` (so
// this should be lower in priority than that), but when we encounter an ambiguous expression that might be
// either `try (foo() ? ...)` or `(try foo()) ? ...`, we should prefer the former. We accomplish that by
// giving it a _static precedence_ of -1 but a _dynamic precedence_ of 1.
prec.dynamic(1, prec.left(-1, $.ternary_expression))
)
)
)
),
await_expression: ($) =>
prec.right(
PRECS.await,
seq(
$._await_operator,
field(
"expr",
choice(
// Prefer direct calls over indirect (same as with `try`).
prec.right(-2, $._expression),
prec.left(0, $.call_expression),
// Special case ternary to `await` the whole thing (same as with `try`).
prec.dynamic(1, prec.left(-1, $.ternary_expression))
)
)
)
),
_await_operator: ($) => alias("await", "await"),
ternary_expression: ($) =>
prec.right(
PRECS.ternary,
seq(
field("condition", $._expression),
$._quest,
field("if_true", $._expression),
":",
field("if_false", $._expr_hack_at_ternary_binary_suffix)
)
),
_expr_hack_at_ternary_binary_suffix: ($) =>
prec.left(
PRECS.ternary_binary_suffix,
choice(
$._expression,
alias($.expr_hack_at_ternary_binary_call, $.call_expression)
)
),
expr_hack_at_ternary_binary_call: ($) =>
seq(
$._expression,
alias($.expr_hack_at_ternary_binary_call_suffix, $.call_suffix)
),
expr_hack_at_ternary_binary_call_suffix: ($) =>
prec(PRECS.call_suffix, $.value_arguments),
call_expression: ($) =>
prec(
PRECS.call,
prec.dynamic(DYNAMIC_PRECS.call, seq($._expression, $.call_suffix))
),
macro_invocation: ($) =>
prec(
PRECS.call,
prec.dynamic(
DYNAMIC_PRECS.call,
seq(
$._hash_symbol,
$.simple_identifier,
optional($.type_parameters),
$.call_suffix
)
)
),
_primary_expression: ($) =>
choice(
$.tuple_expression,
$._basic_literal,
$.lambda_literal,
$.special_literal,
$.playground_literal,
$.array_literal,
$.dictionary_literal,
$.self_expression,
$.super_expression,
$.try_expression,
$.await_expression,
$._referenceable_operator,
$.key_path_expression,
$.key_path_string_expression,
prec.right(
PRECS.fully_open_range,
alias($._three_dot_operator, $.fully_open_range)
)
),
tuple_expression: ($) =>
prec.right(
PRECS.tuple,
seq(
"(",
sep1Opt(
seq(
optional(seq(field("name", $.simple_identifier), ":")),
field("value", $._expression)
),
","
),
")"
)
),
array_literal: ($) =>
seq("[", optional(sep1Opt(field("element", $._expression), ",")), "]"),
dictionary_literal: ($) =>
seq(
"[",
choice(":", sep1Opt($._dictionary_literal_item, ",")),
optional(","),
"]"
),
_dictionary_literal_item: ($) =>
seq(field("key", $._expression), ":", field("value", $._expression)),
special_literal: ($) =>
seq(
$._hash_symbol,
choice(
"file",
"fileID",
"filePath",
"line",
"column",
"function",
"dsohandle"
)
),
playground_literal: ($) =>
seq(
$._hash_symbol,
choice("colorLiteral", "fileLiteral", "imageLiteral"),
"(",
sep1Opt(seq($.simple_identifier, ":", $._expression), ","),
")"
),
lambda_literal: ($) =>
prec.left(
PRECS.lambda,
seq(
choice("{", "^{"),
optional($._lambda_type_declaration),
optional($.statements),
"}"
)
),
_lambda_type_declaration: ($) =>
seq(
repeat($.attribute),
prec(PRECS.expr, optional(field("captures", $.capture_list))),
optional(field("type", $.lambda_function_type)),
"in"
),
capture_list: ($) => seq("[", sep1Opt($.capture_list_item, ","), "]"),
capture_list_item: ($) =>
choice(
field("name", $.self_expression),
prec(
PRECS.expr,
seq(
optional($.ownership_modifier),
field("name", $.simple_identifier),
optional(seq($._equal_sign, field("value", $._expression)))
)
)
),
lambda_function_type: ($) =>
prec(
PRECS.expr,
seq(
choice(
$.lambda_function_type_parameters,
seq("(", optional($.lambda_function_type_parameters), ")")
),
optional($._async_keyword),
optional($.throws),
optional(
seq(
$._arrow_operator,
field("return_type", $._possibly_implicitly_unwrapped_type)
)
)
)
),
lambda_function_type_parameters: ($) => sep1Opt($.lambda_parameter, ","),
lambda_parameter: ($) =>
seq(
choice(
$.self_expression,
prec(PRECS.expr, field("name", $.simple_identifier)),
prec(
PRECS.expr,
seq(
optional(field("external_name", $.simple_identifier)),
field("name", $.simple_identifier),
":",
optional($.parameter_modifiers),
field("type", $._possibly_implicitly_unwrapped_type)
)
)
)
),
self_expression: ($) => "self",
super_expression: ($) => seq("super"),
_else_options: ($) => choice($._block, $.if_statement),
if_statement: ($) =>
prec.right(
PRECS["if"],
seq(
"if",
sep1(field("condition", $._if_condition_sequence_item), ","),
$._block,
optional(seq($["else"], $._else_options))
)
),
_if_condition_sequence_item: ($) =>
choice($._if_let_binding, $._expression, $.availability_condition),
_if_let_binding: ($) =>
seq(
$._direct_or_indirect_binding,
optional(seq($._equal_sign, $._expression)),
optional($.where_clause)
),
guard_statement: ($) =>
prec.right(
PRECS["if"],
seq(
"guard",
sep1(field("condition", $._if_condition_sequence_item), ","),
$["else"],
$._block
)
),
switch_statement: ($) =>
prec.right(
PRECS["switch"],
seq(
"switch",
field("expr", $._expression),
"{",
repeat($.switch_entry),
"}"
)
),
switch_entry: ($) =>
seq(
optional($.modifiers),
choice(
seq(
"case",
seq(
$.switch_pattern,
optional(seq($.where_keyword, $._expression))
),
repeat(seq(",", $.switch_pattern))
),
$.default_keyword
),
":",
$.statements,
optional("fallthrough")
),
switch_pattern: ($) => alias($._binding_pattern_with_expr, $.pattern),
do_statement: ($) =>
prec.right(PRECS["do"], seq("do", $._block, repeat($.catch_block))),
catch_block: ($) =>
seq(
$.catch_keyword,
field("error", optional(alias($._binding_pattern_no_expr, $.pattern))),
optional($.where_clause),
$._block
),
where_clause: ($) => prec.left(seq($.where_keyword, $._expression)),
key_path_expression: ($) =>
prec.right(
PRECS.keypath,
seq(
"\\",
optional(
choice($._simple_user_type, $.array_type, $.dictionary_type)
),
repeat(seq(".", $._key_path_component))
)
),
key_path_string_expression: ($) =>
prec.left(seq($._hash_symbol, "keyPath", "(", $._expression, ")")),
_key_path_component: ($) =>
prec.left(
choice(
seq($.simple_identifier, repeat($._key_path_postfixes)),
repeat1($._key_path_postfixes)
)
),
_key_path_postfixes: ($) =>
choice(
"?",
$.bang,
"self",
seq("[", optional(sep1($.value_argument, ",")), "]")
),
try_operator: ($) =>
prec.right(
seq("try", choice(optional($._try_operator_type), $._fake_try_bang))
),
_try_operator_type: ($) =>
choice(token.immediate("!"), token.immediate("?")),
_assignment_and_operator: ($) =>
choice("+=", "-=", "*=", "/=", "%=", $._equal_sign),
_equality_operator: ($) => choice("!=", "!==", $._eq_eq, "==="),
_comparison_operator: ($) => choice("<", ">", "<=", ">="),
_three_dot_operator: ($) => alias("...", "..."), // Weird alias to satisfy highlight queries
_open_ended_range_operator: ($) => alias("..<", "..<"),
_is_operator: ($) => "is",
_additive_operator: ($) =>
choice(
alias($._plus_then_ws, "+"),
alias($._minus_then_ws, "-"),
"+",
"-"
),
// The `/` operator conflicts with a regex literal (which itself appears to conflict with a
// comment, for some reason), so we must give it equivalent token precedence.
_multiplicative_operator: ($) =>
choice("*", alias(token(prec(PRECS.regex, "/")), "/"), "%"),
as_operator: ($) => choice($._as, $._as_quest, $._as_bang),
_prefix_unary_operator: ($) =>
prec.right(
choice(
"++",
"--",
"-",
"+",
$.bang,
"&",
"~",
$._dot,
$.custom_operator
)
),
_bitwise_binary_operator: ($) => choice("&", "|", "^", "<<", ">>"),
_postfix_unary_operator: ($) => choice("++", "--", $.bang),
directly_assignable_expression: ($) => $._expression,
////////////////////////////////
// Statements - https://docs.swift.org/swift-book/ReferenceManual/Statements.html
////////////////////////////////
statements: ($) =>
prec.left(
// Left precedence is required in switch statements
seq(
$._local_statement,
repeat(seq($._semi, $._local_statement)),
optional($._semi)
)
),
_local_statement: ($) =>
choice(
$._expression,
$._local_declaration,
$._labeled_statement,
$.control_transfer_statement
),
_top_level_statement: ($) =>
choice(
$._expression,
$._global_declaration,
$._labeled_statement,
$._throw_statement
),
_block: ($) => prec(PRECS.block, seq("{", optional($.statements), "}")),
_labeled_statement: ($) =>
seq(
optional($.statement_label),
choice(
$.for_statement,
$.while_statement,
$.repeat_while_statement,
$.do_statement,
$.if_statement,
$.guard_statement,
$.switch_statement
)
),
statement_label: ($) => token(/[a-zA-Z_][a-zA-Z_0-9]*:/),
for_statement: ($) =>
prec(
PRECS.loop,
seq(
"for",
optional($.try_operator),
optional($._await_operator),
field("item", alias($._binding_pattern_no_expr, $.pattern)),
optional($.type_annotation),
"in",
field("collection", $._for_statement_collection),
optional($.where_clause),
$._block
)
),
_for_statement_collection: ($) =>
// If this expression has "await", this triggers some special-cased logic to prefer function calls. We prefer
// the opposite, though, since function calls may contain trailing code blocks, which are undesirable here.
//
// To fix that, we simply undo the special casing by defining our own `await_expression`.
choice($._expression, alias($.for_statement_await, $.await_expression)),
for_statement_await: ($) => seq($._await_operator, $._expression),
while_statement: ($) =>
prec(
PRECS.loop,
seq(
"while",
sep1(field("condition", $._if_condition_sequence_item), ","),
"{",
optional($.statements),
"}"
)
),
repeat_while_statement: ($) =>
prec(
PRECS.loop,
seq(
"repeat",
"{",
optional($.statements),
"}",
// Make sure we make it to the `while` before assuming this is a parameter pack.
repeat($._implicit_semi),
"while",
sep1(field("condition", $._if_condition_sequence_item), ",")
)
),
control_transfer_statement: ($) =>
choice(
prec.right(PRECS.control_transfer, $._throw_statement),
prec.right(
PRECS.control_transfer,
seq(
$._optionally_valueful_control_keyword,
field("result", optional($._expression))
)
)
),
_throw_statement: ($) => seq($.throw_keyword, $._expression),
throw_keyword: ($) => "throw",
_optionally_valueful_control_keyword: ($) =>
choice("return", "continue", "break", "yield"),
assignment: ($) =>
prec.left(
PRECS.assignment,
seq(
field("target", $.directly_assignable_expression),
field("operator", $._assignment_and_operator),
field("result", $._expression)
)
),
value_parameter_pack: ($) =>
prec.left(PRECS.parameter_pack, seq("each", $._expression)),
value_pack_expansion: ($) =>
prec.left(PRECS.parameter_pack, seq("repeat", $._expression)),
availability_condition: ($) =>
seq(
$._hash_symbol,
choice("available", "unavailable"),
"(",
sep1Opt($._availability_argument, ","),
")"
),
_availability_argument: ($) =>
choice(seq($.identifier, sep1($.integer_literal, ".")), "*"),
////////////////////////////////
// Declarations - https://docs.swift.org/swift-book/ReferenceManual/Declarations.html
////////////////////////////////
_global_declaration: ($) =>
choice(
$.import_declaration,
$.property_declaration,
$.typealias_declaration,
$.function_declaration,
$.init_declaration,
$.class_declaration,
$.protocol_declaration,
$.operator_declaration,
$.precedence_group_declaration,
$.associatedtype_declaration,
$.macro_declaration
),
_type_level_declaration: ($) =>
choice(
$.import_declaration,
$.property_declaration,
$.typealias_declaration,
$.function_declaration,
$.init_declaration,
$.class_declaration,
$.protocol_declaration,
$.deinit_declaration,
$.subscript_declaration,
$.operator_declaration,
$.precedence_group_declaration,
$.associatedtype_declaration
),
_local_declaration: ($) =>
choice(
alias($._local_property_declaration, $.property_declaration),
alias($._local_typealias_declaration, $.typealias_declaration),
alias($._local_function_declaration, $.function_declaration),
alias($._local_class_declaration, $.class_declaration)
),
_local_property_declaration: ($) =>
seq(
optional($._locally_permitted_modifiers),
$._modifierless_property_declaration
),
_local_typealias_declaration: ($) =>
seq(
optional($._locally_permitted_modifiers),
$._modifierless_typealias_declaration
),
_local_function_declaration: ($) =>
seq(
optional($._locally_permitted_modifiers),
$._modifierless_function_declaration
),
_local_class_declaration: ($) =>
seq(
optional($._locally_permitted_modifiers),
$._modifierless_class_declaration
),
import_declaration: ($) =>
seq(
optional($.modifiers),
"import",
optional($._import_kind),
$.identifier
),
_import_kind: ($) =>
choice(
"typealias",
"struct",
"class",
"enum",
"protocol",
"let",
"var",
"func"
),
protocol_property_declaration: ($) =>
prec.right(
seq(
optional($.modifiers),
field("name", alias($._binding_kind_and_pattern, $.pattern)),
optional($.type_annotation),
optional($.type_constraints),
$.protocol_property_requirements
)
),
protocol_property_requirements: ($) =>
seq("{", repeat(choice($.getter_specifier, $.setter_specifier)), "}"),
property_declaration: ($) =>
seq(optional($.modifiers), $._modifierless_property_declaration),
_modifierless_property_declaration: ($) =>
prec.right(
seq(
$._possibly_async_binding_pattern_kind,
sep1($._single_modifierless_property_declaration, ",")
)
),
_single_modifierless_property_declaration: ($) =>
prec.left(
seq(
field("name", alias($._no_expr_pattern_already_bound, $.pattern)),
optional($.type_annotation),
optional($.type_constraints),
optional(
choice(
$._expression_with_willset_didset,
$._expression_without_willset_didset,
$.willset_didset_block,
field("computed_value", $.computed_property)
)
)
)
),
_expression_with_willset_didset: ($) =>
prec.dynamic(
1,
seq(
$._equal_sign,
field("value", $._expression),
$.willset_didset_block
)
),
_expression_without_willset_didset: ($) =>
seq($._equal_sign, field("value", $._expression)),
willset_didset_block: ($) =>
choice(
seq("{", $.willset_clause, optional($.didset_clause), "}"),
seq("{", $.didset_clause, optional($.willset_clause), "}")
),
willset_clause: ($) =>
seq(
optional($.modifiers),
"willSet",
optional(seq("(", $.simple_identifier, ")")),
$._block
),
didset_clause: ($) =>
seq(
optional($.modifiers),
"didSet",
optional(seq("(", $.simple_identifier, ")")),
$._block
),
typealias_declaration: ($) =>
seq(optional($.modifiers), $._modifierless_typealias_declaration),
_modifierless_typealias_declaration: ($) =>
seq(
"typealias",
field("name", alias($.simple_identifier, $.type_identifier)),
optional($.type_parameters),
$._equal_sign,
field("value", $._type)
),
function_declaration: ($) =>
prec.right(
seq($._bodyless_function_declaration, field("body", $.function_body))
),
_modifierless_function_declaration: ($) =>
prec.right(
seq(
$._modifierless_function_declaration_no_body,
field("body", $.function_body)
)
),
_bodyless_function_declaration: ($) =>
seq(
optional($.modifiers),
optional("class"), // XXX: This should be possible in non-last position, but that creates parsing ambiguity
$._modifierless_function_declaration_no_body
),
_modifierless_function_declaration_no_body: ($) =>
prec.right(
seq(
$._non_constructor_function_decl,
optional($.type_parameters),
$._function_value_parameters,
optional($._async_keyword),
optional($.throws),
optional(
seq(
$._arrow_operator,
field("return_type", $._possibly_implicitly_unwrapped_type)
)
),
optional($.type_constraints)
)
),
function_body: ($) => $._block,
macro_declaration: ($) =>
seq(
$._macro_head,
$.simple_identifier,
optional($.type_parameters),
$._macro_signature,
optional(field("definition", $.macro_definition)),
optional($.type_constraints)
),
_macro_head: ($) => seq(optional($.modifiers), "macro"),
_macro_signature: ($) =>
seq(
$._function_value_parameters,
optional(seq($._arrow_operator, $._unannotated_type))
),
macro_definition: ($) =>
seq(
$._equal_sign,
field("body", choice($._expression, $.external_macro_definition))
),
external_macro_definition: ($) =>
seq($._hash_symbol, "externalMacro", $.value_arguments),
class_declaration: ($) =>
seq(optional($.modifiers), $._modifierless_class_declaration),
_modifierless_class_decla