Files
carbon-lang/utils/tree_sitter/grammar.js
T
Chandler Carruth 8fd4156616 Update tree_sitter for the new self syntax and static var (#7025)
This implements p7016 for tree_sitter. It also updates the build and
source file to allow this to build successfully and documents how to
successfully run these tests with Bazel given that it is fundamentally
not hermetic.

Assisted-by: Antigravity with Gemini
2026-04-06 19:31:54 +00:00

532 lines
14 KiB
JavaScript

/*
* Part of the Carbon Language project, under the Apache License v2.0 with LLVM
* Exceptions. See /LICENSE for license information.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
// This grammar is more permissive than toolchain because it is geared towards
// editor use.
function repeat_sep1(thing, sep) {
return seq(thing, repeat(seq(sep, thing)));
}
function comma_sep(thing) {
// Trailing comma is only allowed if there is at least one element.
return optional(seq(repeat_sep1(thing, ','), optional(',')));
}
// This is based on toolchain/parser/precedence.cpp
const PREC = {
TermPrefix: 12,
TermPostfix: 12,
NumericPrefix: 11,
NumericPostfix: 11,
Multiplicative: 10,
Additive: 9,
BitwisePrefix: 8,
BitwiseAnd: 7,
BitwiseOr: 7,
BitwiseXor: 7,
BitShift: 7,
TypePostfix: 6,
LogicalPrefix: 5,
Relational: 4,
LogicalAnd: 3,
LogicalOr: 3,
IfExpression: 2,
WhereClause: 1,
};
module.exports = grammar({
name: 'carbon',
word: ($) => $.ident,
conflicts: ($) => [
[$.paren_pattern, $.paren_expression],
[$.struct_literal, $.struct_type_literal],
[$._pattern_without_expression, $._simple_expression],
],
extras: ($) => [/\s/, $.comment],
// NOTE: This must match the order in src/scanner.c, names are not used for matching.
externals: ($) => [$.binary_star, $.postfix_star, $.string],
rules: {
source_file: ($) =>
seq(
optional($.package_directive),
repeat($.import_directive),
repeat($.declaration)
),
api_or_impl: ($) => choice('api', 'impl'),
library_path: ($) => seq('library', $.string),
package_directive: ($) =>
seq('package', $.ident, optional($.library_path), $.api_or_impl, ';'),
import_directive: ($) =>
seq('import', $.ident, optional($.library_path), ';'),
comment: ($) => token(seq('//', /.*/)),
// NOTE: this must be before ident rule to increase its priority.
// https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p2015.md#syntax
numeric_type_literal: ($) => /[iuf][1-9][0-9]*/,
ident: ($) => /[A-Za-z_][A-Za-z0-9_]*/,
bool_literal: ($) => choice('true', 'false'),
numeric_literal: ($) => {
// This is using variables because rules are not allowed in
// token.immediate and token.
// https://github.com/tree-sitter/tree-sitter/issues/449
const decimal_integer_literal = choice('0', /[1-9](_?[0-9])*/);
const hex_digits = /[0-9A-F](_?[0-9A-F])*/;
const binary_integer_literal = /0b[01](_?[01])*/;
const hex_integer_literal = seq('0x', token.immediate(hex_digits));
const decimal_real_number_literal = seq(
decimal_integer_literal,
token.immediate(/\.[0-9](_?[0-9])*/),
optional(
seq(
token.immediate(/e[+-]?/),
token.immediate(decimal_integer_literal)
)
)
);
const hex_real_number_literal = seq(
hex_integer_literal,
token.immediate('.'),
token.immediate(hex_digits),
optional(
seq(
token.immediate(/p[+-]?/),
token.immediate(decimal_integer_literal)
)
)
);
return token(
choice(
decimal_integer_literal,
binary_integer_literal,
hex_integer_literal,
decimal_real_number_literal,
hex_real_number_literal
)
);
},
array_literal: ($) =>
seq(
'[',
field('type', $._expression),
';',
optional(field('size', $._expression)),
']'
),
struct_literal: ($) =>
seq('{', comma_sep(seq($.designator, '=', $._expression)), '}'),
struct_type_literal: ($) =>
seq('{', comma_sep(seq($.designator, ':', $._expression)), '}'),
builtin_type: ($) => choice('Self', 'String', 'bool', 'type'),
literal: ($) =>
choice(
$.bool_literal,
$.numeric_literal,
$.numeric_type_literal,
$.string,
$.struct_literal,
$.struct_type_literal
),
binding_lhs: ($) => choice($.ident, '_'),
paren_pattern: ($) =>
seq(
'(',
comma_sep(choice($._pattern_without_expression, $._expression)),
')'
),
_pattern_without_expression: ($) =>
choice(
'auto',
seq(optional('ref'), 'self', optional(seq(':', $._expression))),
seq($.binding_lhs, ':', $._expression),
seq($.binding_lhs, ':!', $._expression),
seq('template', $.binding_lhs, ':!', $._expression),
seq('var', $._pattern),
$.paren_pattern,
// alternative patterns
// example: Optional(i32).Some(x: i32)
seq($._simple_expression, $.paren_pattern)
),
_pattern: ($) =>
choice($._pattern_without_expression, $._simple_expression),
unary_prefix_expression: ($) => {
const table = [
[PREC.NumericPrefix, '-'],
[PREC.NumericPrefix, '--'],
[PREC.NumericPrefix, '++'],
[PREC.BitwisePrefix, '^'],
[PREC.LogicalPrefix, 'not'],
];
return choice(
...table.map(([precedence, operator]) =>
prec(
precedence,
seq(field('operator', operator), field('value', $._expression))
)
)
);
},
binary_expression: ($) => {
const table = [
[PREC.LogicalAnd, 'and'],
[PREC.LogicalOr, 'or'],
[PREC.BitwiseAnd, '&'],
[PREC.BitwiseOr, '|'],
[PREC.BitwiseXor, '^'],
[PREC.BitShift, choice('<<', '>>')],
[PREC.Relational, choice('==', '!=', '<', '<=', '>', '>=')],
[PREC.Additive, choice('+', '-')],
[PREC.Multiplicative, choice($.binary_star, '/', '%')],
];
return choice(
...table.map(([precedence, operator]) =>
prec.left(
precedence,
seq(
field('left', $._expression),
field('operator', operator),
field('right', $._expression)
)
)
)
);
},
// This should be non-associative but conflicts are not allowed in tree-sitter
as_expression: ($) => prec.left(seq($._expression, 'as', $._expression)),
ref_expression: ($) =>
prec.right(PREC.TermPrefix, seq('&', $._simple_expression)),
deref_expression: ($) =>
prec.right(PREC.TermPrefix, seq('*', $._simple_expression)),
fn_type_expression: ($) =>
prec.left(seq('__Fn', $.paren_expression, '->', $._simple_expression)),
if_expression: ($) =>
prec(
PREC.IfExpression,
seq('if', $._expression, 'then', $._expression, 'else', $._expression)
),
paren_expression: ($) => seq('(', comma_sep($._expression), ')'),
index_expression: ($) =>
prec(
PREC.TermPostfix,
seq($._simple_expression, '[', $._expression, ']')
),
designator: ($) => seq('.', choice('base', $.ident)),
postfix_expression: ($) =>
prec(
PREC.TermPostfix,
seq(
$._simple_expression,
choice(
'++',
'--',
$.designator,
seq('->', $.ident),
seq(choice('.', '->'), '(', $._expression, ')')
)
)
),
where_clause: ($) =>
choice(
seq($._simple_expression, '==', $._simple_expression),
seq($._simple_expression, 'impls', $._simple_expression),
seq($._simple_expression, '=', $._simple_expression),
prec.left(
PREC.WhereClause + 1,
seq($.where_clause, 'and', $.where_clause)
)
),
where_expression: ($) =>
prec.left(PREC.WhereClause, seq($._expression, 'where', $.where_clause)),
call_expression: ($) =>
prec(PREC.TermPostfix, seq($._simple_expression, $.paren_expression)),
pointer_expression: ($) =>
prec(PREC.TypePostfix, seq($._simple_expression, $.postfix_star)),
_simple_expression: ($) =>
choice(
$.array_literal,
$.builtin_type,
$.call_expression,
$.deref_expression,
$.fn_type_expression,
$.ident,
$.index_expression,
$.literal,
$.paren_expression,
$.pointer_expression,
$.postfix_expression,
$.ref_expression,
'self',
'.Self',
$.designator
),
_expression: ($) =>
choice(
$.as_expression,
$.binary_expression,
$.if_expression,
$.unary_prefix_expression,
$.where_expression,
$._simple_expression
),
var_declaration: ($) =>
seq(
seq(optional('static'), 'var'),
$._pattern_without_expression,
optional(seq('=', $._expression)),
';'
),
let_declaration: ($) =>
seq('let', $._pattern_without_expression, '=', $._expression, ';'),
assign_statement: ($) =>
seq($._expression, $._assign_operator, $._expression, ';'),
_assign_operator: ($) =>
choice('=', '+=', '/=', '*=', '%=', '-=', '&=', '|=', '^=', '<<=', '>>='),
match_clause: ($) =>
seq(choice(seq('case', $._pattern), 'default'), '=>', $.block),
match_statement: ($) =>
seq('match', '(', $._expression, ')', '{', repeat($.match_clause), '}'),
returned_var_statement: ($) => seq('returned', $.var_declaration),
while_statement: ($) => seq('while', '(', $._expression, ')', $.block),
break_statement: ($) => seq('break', ';'),
continue_statement: ($) => seq('continue', ';'),
return_statement: ($) =>
seq('return', optional(choice('var', $._expression)), ';'),
if_statement: ($) =>
seq('if', '(', $._expression, ')', $.block, optional($.else)),
else: ($) => choice(seq('else', $.if_statement), seq('else', $.block)),
for_statement: ($) =>
seq('for', '(', $._pattern, 'in', $._expression, ')', $.block),
statement: ($) =>
choice(
seq($._expression, ';'),
$.assign_statement,
$.var_declaration,
$.let_declaration,
$.match_statement,
$.returned_var_statement,
$.if_statement,
$.while_statement,
$.break_statement,
$.continue_statement,
$.return_statement,
$.for_statement
),
block: ($) => seq('{', repeat($.statement), '}'),
declared_name: ($) => repeat_sep1($.ident, '.'),
generic_binding: ($) =>
seq(optional('template'), $.ident, ':!', $._expression),
deduced_param: ($) => $.generic_binding,
deduced_params: ($) => seq('[', comma_sep($.deduced_param), ']'),
return_type: ($) => seq('->', choice('auto', $._expression)),
function_declaration: ($) =>
seq(
optional(choice('abstract', 'virtual', 'impl')),
'fn',
$.declared_name,
optional($.deduced_params),
$.paren_pattern,
optional($.return_type),
choice($.block, ';')
),
namespace_declaration: ($) => seq('namespace', $.declared_name, ';'),
alias_declaration: ($) =>
seq('alias', $.declared_name, '=', $._expression, ';'),
type_params: ($) => $.paren_pattern,
interface_body_item: ($) =>
choice(
$.function_declaration,
seq('let', $.generic_binding, ';'),
seq('extend', $._expression, ';'),
seq('require', $._expression, 'impls', $._expression, ';')
),
interface_body: ($) => seq('{', repeat($.interface_body_item), '}'),
interface_declaration: ($) =>
seq(
'interface',
$.declared_name,
optional($.deduced_params),
optional($.type_params),
choice(';', $.interface_body)
),
constraint_declaration: ($) =>
seq(
'constraint',
$.declared_name,
optional($.deduced_params),
optional($.type_params),
choice(';', $.interface_body)
),
impl_body_item: ($) => choice($.function_declaration, $.alias_declaration),
impl_body: ($) => seq('{', repeat($.impl_body_item), '}'),
impl_declaration: ($) =>
seq(
'impl',
optional(seq('forall', $.deduced_params)),
optional($._expression),
'as',
$._expression,
$.impl_body
),
extend_impl_declaration: ($) =>
seq('extend', 'impl', 'as', $._expression, $.impl_body),
extend_base_declaration: ($) =>
seq('extend', 'base', ':', $._expression, ';'),
destructor_declaration: ($) =>
seq(
optional(choice('virtual', 'impl')),
'destructor',
optional($.deduced_params),
choice($.block, ';')
),
class_body_item: ($) =>
choice(
$.declaration,
$.extend_base_declaration,
$.extend_impl_declaration,
$.mix_declaration,
$.destructor_declaration
),
class_body: ($) => seq('{', repeat($.class_body_item), '}'),
class_declaration: ($) =>
seq(
optional(choice('base', 'abstract')),
'class',
$.declared_name,
optional($.deduced_params),
optional($.type_params),
choice(';', $.class_body)
),
choice_declaration: ($) =>
seq(
'choice',
$.declared_name,
optional($.type_params),
'{',
comma_sep(seq($.ident, optional($.paren_expression))),
'}'
),
empty_declaration: ($) => ';',
declaration: ($) =>
choice(
$.empty_declaration,
$.namespace_declaration,
$.var_declaration,
$.let_declaration,
$.function_declaration,
$.alias_declaration,
$.interface_declaration,
$.constraint_declaration,
$.impl_declaration,
$.class_declaration,
$.choice_declaration,
$.mixin_declaration,
$.match_first_declaration
),
// Explorer only experimental featurues
mix_declaration: ($) => seq('__mix', $._expression, ';'),
mixin_declaration: ($) =>
seq(
'__mixin',
$.declared_name,
optional($.type_params),
optional(seq('for', $._expression)),
'{',
repeat(choice($.function_declaration, $.mix_declaration)),
'}'
),
match_first_declaration: ($) =>
seq('__match_first', '{', repeat($.impl_declaration), '}'),
},
});