mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 20:20:10 +01:00
The WORKSPACE file is deprecated; support is already off by default, and it'll be removed in the next major bazel release. Our main dependency is tree-sitter, and I'm trying to address that here. We're currently using https://github.com/elliottt/rules_tree_sitter, but that hasn't been updated in a couple years, meaning it lacks MODULE.bazel support. In the registry, there's https://registry.bazel.build/modules/tree-sitter-bazel, but this is only the *parser* libraries of tree-sitter, not the *generator*. I'm using it for that much, at least. For the *generator*, which transforms grammar.js to parser.c/h, I'm just requiring a non-hermetic invocation (i.e., people who want to work on it will need to install tree-sitter; see the README.md updates). I tried running it manually, but parser.c is about 600 KB; pre-commit rejects files that large and I don't think an exception makes sense to override for this (it'd probably also grow substantially if the grammar were updated to cover more syntax). In order to make the non-hermetic call not break "bazel build //..." for most developers, I'm marking most targets in the package as manual. Note, I did look long and hard at using `aspect_rules_js`/`rules_nodejs` to invoke npm. This took a lot of time, and I have a commit that's mostly working, except I hit a point where it uses `declare_symlink` which we disallow for compatibility reasons (commit "Lots of work for figuring out rule_js uses declare_symlink" on the PR). As a consequence, I think we can't use the primary supported ways to have hermetic npm calls. Also, `treesitter` -> `tree_sitter` because it's generally called `tree-sitter`, two words. We even had a `treesitter/src/tree_sitter` directory so it's a bit inconsistent. As far as bugs here, the parser library breaks bazel queries, e.g. the error: ``` ERROR: Evaluation of query "somepath(//..., @llvm-project//third-party/unittest:gtest)" failed: preloading transitive closure failed: no such package '@@[unknown repo 'platforms' requested from @@tree-sitter-bazel+]//': The repository '@@[unknown repo 'platforms' requested from @@tree-sitter-bazel+]' could not be resolved: No repository visible as '@platforms' from repository '@@tree-sitter-bazel+' ``` I'm just excluding tree_sitter from queries where I can to work around the error.
534 lines
14 KiB
JavaScript
534 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],
|
|
],
|
|
|
|
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($.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(
|
|
'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: ($) =>
|
|
choice(
|
|
$.generic_binding,
|
|
seq(optional('addr'), 'self', ':', $._expression)
|
|
),
|
|
|
|
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), '}'),
|
|
},
|
|
});
|