text: const PREC = {
    // this resolves a conflict between the usage of ':' in a lambda vs in a
    // typed parameter. In the case of a lambda, we don't allow typed parameters.
    lambda: -2,
    typed_parameter: -1,
    conditional: -1,
  
    parenthesized_expression: 1,
    parenthesized_list_splat: 1,
    not: 1,
    compare: 2,
    or: 10,
    and: 11,
    bitwise_or: 12,
    bitwise_and: 13,
    xor: 14,
    shift: 15,
    plus: 16,
    times: 17,
    unary: 18,
    power: 19,
    call: 20,
  }
  
  module.exports = grammar({
    name: 'python',
  
    extras: $ => [
      $.comment,
      /[\s\f\uFEFF\u2060\u200B]|\\\r?\n/
    ],
  
    conflicts: $ => [
      [$.primary_expression, $.pattern],
      [$.primary_expression, $.list_splat_pattern],
      [$.tuple, $.tuple_pattern],
      [$.list, $.list_pattern],
      [$.with_item, $._collection_elements],
    ],
  
    supertypes: $ => [
      $._simple_statement,
      $._compound_statement,
      $.expression,
      $.primary_expression,
      $.pattern,
      $.parameter,
    ],
  
    externals: $ => [
      $._newline,
      $._indent,
      $._dedent,
      $._string_start,
      $._string_content,
      $._string_end,
    ],
  
    inline: $ => [
      $._simple_statement,
      $._compound_statement,
      $._suite,
      $._expressions,
      $._left_hand_side,
      $.keyword_identifier,
    ],
  
    word: $ => $.identifier,
  
    rules: {
      module: $ => repeat($._statement),
  
      _statement: $ => choice(
        $._simple_statements,
        $._compound_statement
      ),
  
      // Simple statements
  
      _simple_statements: $ => seq(
        $._simple_statement,
        optional(repeat(seq(
          $._semicolon,
          $._simple_statement
        ))),
        optional($._semicolon),
        $._newline
      ),
  
      _simple_statement: $ => choice(
        $.future_import_statement,
        $.import_statement,
        $.import_from_statement,
        $.print_statement,
        $.assert_statement,
        $.expression_statement,
        $.return_statement,
        $.delete_statement,
        $.raise_statement,
        $.pass_statement,
        $.break_statement,
        $.continue_statement,
        $.global_statement,
        $.nonlocal_statement,
        $.exec_statement
      ),
  
      import_statement: $ => seq(
        'import',
        $._import_list
      ),
  
      import_prefix: $ => repeat1('.'),
  
      relative_import: $ => seq(
        $.import_prefix,
        optional($.dotted_name)
      ),
  
      future_import_statement: $ => seq(
        'from',
        '__future__',
        'import',
        choice(
          $._import_list,
          seq('(', $._import_list, ')'),
        )
      ),
  
      import_from_statement: $ => seq(
        'from',
        field('module_name', choice(
          $.relative_import,
          $.dotted_name
        )),
        'import',
        choice(
          $.wildcard_import,
          $._import_list,
          seq('(', $._import_list, ')')
        )
      ),
  
      _import_list: $ => seq(
        commaSep1(field('name', choice(
          $.dotted_name,
          $.aliased_import
        ))),
        optional(',')
      ),
  
      aliased_import: $ => seq(
        field('name', $.dotted_name),
        'as',
        field('alias', $.identifier)
      ),
  
      wildcard_import: $ => '*',
  
      print_statement: $ => choice(
        prec(1, seq(
          'print',
          $.chevron,
          repeat(seq(',', field('argument', $.expression))),
          optional(','))
        ),
        prec(-10, seq(
          'print',
          commaSep1(field('argument', $.expression)),
          optional(',')
        ))
      ),
  
      chevron: $ => seq(
        '>>',
        $.expression
      ),
  
      assert_statement: $ => seq(
        'assert',
        commaSep1($.expression)
      ),
  
      expression_statement: $ => choice(
        $.expression,
        seq(commaSep1($.expression), optional(',')),
        $.assignment,
        $.augmented_assignment,
        $.yield
      ),
  
      named_expression: $ => seq(
        field('name', $.identifier),
        ':=',
        field('value', $.expression)
      ),
  
      return_statement: $ => seq(
        'return',
        optional($._expressions)
      ),
  
      delete_statement: $ => seq(
        'del',
        $._expressions
      ),
  
      _expressions: $ => choice(
        $.expression,
        $.expression_list
      ),
  
      raise_statement: $ => seq(
        'raise',
        optional($._expressions),
        optional(seq('from', field('cause', $.expression)))
      ),
  
      pass_statement: $ => prec.left('pass'),
      break_statement: $ => prec.left('break'),
      continue_statement: $ => prec.left('continue'),
  
      // Compound statements
  
      _compound_statement: $ => choice(
        $.if_statement,
        $.for_statement,
        $.while_statement,
        $.try_statement,
        $.with_statement,
        $.function_definition,
        $.class_definition,
        $.decorated_definition
      ),
  
      if_statement: $ => seq(
        'if',
        field('condition', $.expression),
        ':',
        field('consequence', $._suite),
        repeat(field('alternative', $.elif_clause)),
        optional(field('alternative', $.else_clause))
      ),
  
      elif_clause: $ => seq(
        'elif',
        field('condition', $.expression),
        ':',
        field('consequence', $._suite)
      ),
  
      else_clause: $ => seq(
        'else',
        ':',
        field('body', $._suite)
      ),
  
      for_statement: $ => seq(
        optional('async'),
        'for',
        field('left', $._left_hand_side),
        'in',
        field('right', $._expressions),
        ':',
        field('body', $._suite),
        field('alternative', optional($.else_clause))
      ),
  
      while_statement: $ => seq(
        'while',
        field('condition', $.expression),
        ':',
        field('body', $._suite),
        optional(field('alternative', $.else_clause))
      ),
  
      try_statement: $ => seq(
        'try',
        ':',
        field('body', $._suite),
        choice(
          seq(
            repeat1($.except_clause),
            optional($.else_clause),
            optional($.finally_clause)
          ),
          $.finally_clause
        )
      ),
  
      except_clause: $ => seq(
        'except',
        optional(seq(
          $.expression,
          optional(seq(
            choice('as', ','),
            $.expression
          ))
        )),
        ':',
        $._suite
      ),
  
      finally_clause: $ => seq(
        'finally',
        ':',
        $._suite
      ),
  
      with_statement: $ => seq(
        optional('async'),
        'with',
        $.with_clause,
        ':',
        field('body', $._suite)
      ),
  
      with_clause: $ => choice(
        commaSep1($.with_item),
        seq('(', commaSep1($.with_item), ')')
      ),
  
      with_item: $ => prec.dynamic(-1, seq(
        field('value', $.expression),
        optional(seq(
          'as',
          field('alias', $.pattern)
        ))
      )),
  
      function_definition: $ => seq(
        optional('async'),
        'def',
        field('name', $.identifier),
        field('parameters', $.parameters),
        optional(
          seq(
            '->',
            field('return_type', $.type)
          )
        ),
        ':',
        field('body', $._suite)
      ),
  
      parameters: $ => seq(
        '(',
        optional($._parameters),
        ')'
      ),
  
      lambda_parameters: $ => $._parameters,
  
      list_splat: $ => seq(
        '*',
        $.expression,
      ),
  
      dictionary_splat: $ => seq(
        '**',
        $.expression
      ),
  
      global_statement: $ => seq(
        'global',
        commaSep1($.identifier)
      ),
  
      nonlocal_statement: $ => seq(
        'nonlocal',
        commaSep1($.identifier)
      ),
  
      exec_statement: $ => seq(
        'exec',
        field('code', $.string),
        optional(
          seq(
            'in',
            commaSep1($.expression)
          )
        )
      ),
  
      class_definition: $ => seq(
        'class',
        field('name', $.identifier),
        field('superclasses', optional($.argument_list)),
        ':',
        field('body', $._suite)
      ),
  
      parenthesized_list_splat: $ => prec(PREC.parenthesized_list_splat, seq(
        '(',
        choice(
          alias($.parenthesized_list_splat, $.parenthesized_expression),
          $.list_splat,
        ),
        ')',
      )),
  
      argument_list: $ => seq(
        '(',
        optional(commaSep1(
          choice(
            $.expression,
            $.list_splat,
            $.dictionary_splat,
            alias($.parenthesized_list_splat, $.parenthesized_expression),
            $.keyword_argument
          )
        )),
        optional(','),
        ')'
      ),
  
      decorated_definition: $ => seq(
        repeat1($.decorator),
        field('definition', choice(
          $.class_definition,
          $.function_definition
        ))
      ),
  
      decorator: $ => seq(
        '@',
        $.primary_expression,
        $._newline
      ),
  
      _suite: $ => choice(
        alias($._simple_statements, $.block),
        seq($._indent, $.block),
        alias($._newline, $.block)
      ),
  
      block: $ => seq(
        repeat($._statement),
        $._dedent
      ),
  
      expression_list: $ => prec.right(seq(
        choice($.expression, $.list_splat),
        choice(
          ',',
          seq(
            repeat1(seq(
              ',',
              choice($.expression, $.list_splat)
            )),
            optional(',')
          ),
        )
      )),
  
      dotted_name: $ => sep1($.identifier, '.'),
  
      // Patterns
  
      _parameters: $ => seq(
        commaSep1($.parameter),
        optional(',')
      ),
  
      _patterns: $ => seq(
        commaSep1($.pattern),
        optional(',')
      ),
  
      parameter: $ => choice(
        $.identifier,
        $.typed_parameter,
        $.default_parameter,
        $.typed_default_parameter,
        $.list_splat_pattern,
        $.tuple_pattern,
        alias('*', $.list_splat_pattern),
        $.dictionary_splat_pattern
      ),
  
      pattern: $ => choice(
        $.identifier,
        $.keyword_identifier,
        $.subscript,
        $.attribute,
        $.list_splat_pattern,
        $.tuple_pattern,
        $.list_pattern
      ),
  
      tuple_pattern: $ => seq(
        '(',
        optional($._patterns),
        ')'
      ),
  
      list_pattern: $ => seq(
        '[',
        optional($._patterns),
        ']'
      ),
  
      default_parameter: $ => seq(
        field('name', $.identifier),
        '=',
        field('value', $.expression)
      ),
  
      typed_default_parameter: $ => prec(PREC.typed_parameter, seq(
        field('name', $.identifier),
        ':',
        field('type', $.type),
        '=',
        field('value', $.expression)
      )),
  
      list_splat_pattern: $ => seq(
        '*',
        choice($.identifier, $.keyword_identifier, $.subscript, $.attribute)
      ),
  
      dictionary_splat_pattern: $ => seq(
        '**',
        choice($.identifier, $.keyword_identifier, $.subscript, $.attribute)
      ),
  
      // Expressions
  
      _expression_within_for_in_clause: $ => choice(
        $.expression,
        alias($.lambda_within_for_in_clause, $.lambda)
      ),
  
      expression: $ => choice(
        $.comparison_operator,
        $.not_operator,
        $.boolean_operator,
        $.await,
        $.lambda,
        $.primary_expression,
        $.conditional_expression,
        $.named_expression
      ),
  
      primary_expression: $ => choice(
        $.binary_operator,
        $.identifier,
        $.keyword_identifier,
        $.string,
        $.concatenated_string,
        $.integer,
        $.float,
        $.true,
        $.false,
        $.none,
        $.unary_operator,
        $.attribute,
        $.subscript,
        $.call,
        $.list,
        $.list_comprehension,
        $.dictionary,
        $.dictionary_comprehension,
        $.set,
        $.set_comprehension,
        $.tuple,
        $.parenthesized_expression,
        $.generator_expression,
        $.ellipsis
      ),
  
      not_operator: $ => prec(PREC.not, seq(
        'not',
        field('argument', $.expression)
      )),
  
      boolean_operator: $ => choice(
        prec.left(PREC.and, seq(
          field('left', $.expression),
          field('operator', 'and'),
          field('right', $.expression)
        )),
        prec.left(PREC.or, seq(
          field('left', $.expression),
          field('operator', 'or'),
          field('right', $.expression)
        ))
      ),
  
      binary_operator: $ => {
        const table = [
          [prec.left, '+', PREC.plus],
          [prec.left, '-', PREC.plus],
          [prec.left, '*', PREC.times],
          [prec.left, '@', PREC.times],
          [prec.left, '/', PREC.times],
          [prec.left, '%', PREC.times],
          [prec.left, '//', PREC.times],
          [prec.right, '**', PREC.power],
          [prec.left, '|', PREC.bitwise_or],
          [prec.left, '&', PREC.bitwise_and],
          [prec.left, '^', PREC.xor],
          [prec.left, '<<', PREC.shift],
          [prec.left, '>>', PREC.shift],
        ];
  
        return choice(...table.map(([fn, operator, precedence]) => fn(precedence, seq(
          field('left', $.primary_expression),
          field('operator', operator),
          field('right', $.primary_expression)
        ))));
      },
  
      unary_operator: $ => prec(PREC.unary, seq(
        field('operator', choice('+', '-', '~')),
        field('argument', $.primary_expression)
      )),
  
      comparison_operator: $ => prec.left(PREC.compare, seq(
        $.primary_expression,
        repeat1(seq(
          field('operators',
            choice(
              '<',
              '<=',
              '==',
              '!=',
              '>=',
              '>',
              '<>',
              'in',
              seq('not', 'in'),
              'is',
              seq('is', 'not')
            )),
            $.primary_expression
          ))
      )),
  
      lambda: $ => prec(PREC.lambda, seq(
        'lambda',
        field('parameters', optional($.lambda_parameters)),
        ':',
        field('body', $.expression)
      )),
  
      lambda_within_for_in_clause: $ => seq(
        'lambda',
        field('parameters', optional($.lambda_parameters)),
        ':',
        field('body', $._expression_within_for_in_clause)
      ),
  
      assignment: $ => seq(
        field('left', $._left_hand_side),
        choice(
          seq('=', field('right', $._right_hand_side)),
          seq(':', field('type', $.type)),
          seq(':', field('type', $.type), '=', field('right', $._right_hand_side))
        )
      ),
  
      augmented_assignment: $ => seq(
        field('left', $._left_hand_side),
        field('operator', choice(
          '+=', '-=', '*=', '/=', '@=', '//=', '%=', '**=',
          '>>=', '<<=', '&=', '^=', '|='
        )),
        field('right', $._right_hand_side)
      ),
  
      _left_hand_side: $ => choice(
        $.pattern,
        $.pattern_list
      ),
  
      pattern_list: $ => seq(
        $.pattern,
        choice(
          ',',
          seq(
            repeat1(seq(
              ',',
              $.pattern
            )),
            optional(',')
          )
        )
      ),
  
      _right_hand_side: $ => choice(
        $.expression,
        $.expression_list,
        $.assignment,
        $.augmented_assignment,
        $.yield
      ),
  
      yield: $ => prec.right(seq(
        'yield',
        choice(
          seq(
            'from',
            $.expression
          ),
          optional($._expressions)
        )
      )),
  
      attribute: $ => prec(PREC.call, seq(
        field('object', $.primary_expression),
        '.',
        field('attribute', $.identifier)
      )),
  
      subscript: $ => prec(PREC.call, seq(
        field('value', $.primary_expression),
        '[',
        commaSep1(field('subscript', choice($.expression, $.slice))),
        optional(','),
        ']'
      )),
  
      slice: $ => seq(
        optional($.expression),
        ':',
        optional($.expression),
        optional(seq(':', optional($.expression)))
      ),
  
      ellipsis: $ => '...',
  
      call: $ => prec(PREC.call, seq(
        field('function', $.primary_expression),
        field('arguments', choice(
          $.generator_expression,
          $.argument_list
        ))
      )),
  
      typed_parameter: $ => prec(PREC.typed_parameter, seq(
        choice(
          $.identifier,
          $.list_splat_pattern,
          $.dictionary_splat_pattern
        ),
        ':',
        field('type', $.type)
      )),
  
      type: $ => $.expression,
  
      keyword_argument: $ => seq(
        field('name', choice($.identifier, $.keyword_identifier)),
        '=',
        field('value', $.expression)
      ),
  
      // Literals
  
      list: $ => seq(
        '[',
        optional($._collection_elements),
        ']'
      ),
  
      set: $ => seq(
        '{',
        $._collection_elements,
        '}'
      ),
  
      tuple: $ => seq(
        '(',
        optional($._collection_elements),
        ')'
      ),
  
      dictionary: $ => seq(
        '{',
        optional(commaSep1(choice($.pair, $.dictionary_splat))),
        optional(','),
        '}'
      ),
  
      pair: $ => seq(
        field('key', $.expression),
        ':',
        field('value', $.expression)
      ),
  
      list_comprehension: $ => seq(
        '[',
        field('body', $.expression),
        $._comprehension_clauses,
        ']'
      ),
  
      dictionary_comprehension: $ => seq(
        '{',
        field('body', $.pair),
        $._comprehension_clauses,
        '}'
      ),
  
      set_comprehension: $ => seq(
        '{',
        field('body', $.expression),
        $._comprehension_clauses,
        '}'
      ),
  
      generator_expression: $ => seq(
        '(',
        field('body', $.expression),
        $._comprehension_clauses,
        ')'
      ),
  
      _comprehension_clauses: $ => seq(
        $.for_in_clause,
        repeat(choice(
          $.for_in_clause,
          $.if_clause
        ))
      ),
  
      parenthesized_expression: $ => prec(PREC.parenthesized_expression, seq(
        '(',
        choice($.expression, $.yield),
        ')'
      )),
  
      _collection_elements: $ => seq(
        commaSep1(choice(
          $.expression, $.yield, $.list_splat, $.parenthesized_list_splat
        )),
        optional(',')
      ),
  
      for_in_clause: $ => prec.left(seq(
        optional('async'),
        'for',
        field('left', $._left_hand_side),
        'in',
        field('right', commaSep1($._expression_within_for_in_clause)),
        optional(',')
      )),
  
      if_clause: $ => seq(
        'if',
        $.expression
      ),
  
      conditional_expression: $ => prec.right(PREC.conditional, seq(
        $.expression,
        'if',
        $.expression,
        'else',
        $.expression
      )),
  
      concatenated_string: $ => seq(
        $.string,
        repeat1($.string)
      ),
  
      string: $ => seq(
        alias($._string_start, '"'),
        repeat(choice($.interpolation, $._escape_interpolation, $.escape_sequence, $._not_escape_sequence, $._string_content)),
        alias($._string_end, '"')
      ),
  
      interpolation: $ => seq(
        '{',
        $.expression,
        optional($.type_conversion),
        optional($.format_specifier),
        '}'
      ),
  
      _escape_interpolation: $ => choice('{{', '}}'),
  
      escape_sequence: $ => token(prec(1, seq(
        '\\',
        choice(
          /u[a-fA-F\d]{4}/,
          /U[a-fA-F\d]{8}/,
          /x[a-fA-F\d]{2}/,
          /\d{3}/,
          /\r?\n/,
          /['"abfrntv\\]/,
        )
      ))),
  
      _not_escape_sequence: $ => '\\',
  
      format_specifier: $ => seq(
        ':',
        repeat(choice(
          token(prec(1, /[^{}\n]+/)),
          $.format_expression
        ))
      ),
  
      format_expression: $ => seq('{', $.expression, '}'),
  
      type_conversion: $ => /![a-z]/,
  
      integer: $ => token(choice(
        seq(
          choice('0x', '0X'),
          repeat1(/_?[A-Fa-f0-9]+/),
          optional(/[Ll]/)
        ),
        seq(
          choice('0o', '0O'),
          repeat1(/_?[0-7]+/),
          optional(/[Ll]/)
        ),
        seq(
          choice('0b', '0B'),
          repeat1(/_?[0-1]+/),
          optional(/[Ll]/)
        ),
        seq(
          repeat1(/[0-9]+_?/),
          choice(
            optional(/[Ll]/), // long numbers
            optional(/[jJ]/) // complex numbers
          )
        )
      )),
  
      float: $ => {
        const digits = repeat1(/[0-9]+_?/);
        const exponent = seq(/[eE][\+-]?/, digits)
  
        return token(seq(
          choice(
            seq(digits, '.', optional(digits), optional(exponent)),
            seq(optional(digits), '.', digits, optional(exponent)),
            seq(digits, exponent)
          ),
          optional(choice(/[Ll]/, /[jJ]/))
        ))
      },
  
      identifier: $ => /[_\p{XID_Start}][_\p{XID_Continue}]*/,
  
      keyword_identifier: $ => prec(-3, alias(
        choice(
          'print',
          'exec',
          'async',
          'await',
        ),
        $.identifier
      )),
  
      true: $ => 'True',
      false: $ => 'False',
      none: $ => 'None',
  
      await: $ => prec(PREC.unary, seq(
        'await',
        $.expression
      )),
  
      comment: $ => token(seq('#', /.*/)),
  
      _semicolon: $ => ';'
    }
  })
  
  function commaSep1 (rule) {
    return sep1(rule, ',')
  }
  
  function sep1 (rule, separator) {
    return seq(rule, repeat(seq(separator, rule)))
  }
  