Files
carbon-lang/utils/tree_sitter/grammar.js
T
Chandler CarruthandRichard Smith 94d2c1c6d4 Make proposal filenames use 6 digits and include the title (#7245)
We've talked about adding the title to the filename several times over
the years and it seems really valuable. This requires us to compute a
"slug" for the title spelling that can be part of the filename.

Beyond that, we crossed 7000 recently, and so it seems likely that we
will need to add digits sooner rather than later here, so this goes
ahead and moves us to 6 digits so we don't have to adjust again for a
reasonable length of time.

To implement this and ensure we can sustain it going forward this adds a
tool to our pre-commit that validates (and corrects if needed) the
filename.

In order to update everything and keep links working, there are a _lot_
of changes, but the most interesting for direct review are in
`proposals/scripts`.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-05-29 00:55:04 +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/p002015.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), '}'),
},
});