mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-01 22:02:45 +01:00
Most of these are places where we failed to include a header file and simply never got an error about this. The fix is to include the header file. Most other cases are functions that should have been marked `static` but were not. Finding all of these was a main motivation for me enabling the warning despite how much work it is. One complicating factor was that we weren't including the `handle.h` for all the state-based handler functions. While this isn't a tiny amount of code, it is just declarations and doesn't add any extra dependencies. It also lets us have the checking for which functions need to be `static` and which don't. For the `parse` library I had to add the `handle.h` header as well, I tried to match the design of it in `check`. I have also had to work around a bug in the warning, but given the value it seems to be providing, that seems reasonable. I've filed the bug upstream: https://github.com/llvm/llvm-project/issues/94138 I also had to use some hacks to work around limitations of Bazel rules that wrap `cc_library` rules and don't expose `copts`. I filed a bug for `cc_proto_library` specifically: ~https://github.com/bazelbuild/bazel/issues/22610~ https://github.com/bazelbuild/bazel/issues/4446
182 lines
5.6 KiB
C
182 lines
5.6 KiB
C
// 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
|
|
|
|
#include "tree_sitter/parser.h"
|
|
|
|
enum TokenType {
|
|
BINARY_STAR,
|
|
POSTFIX_STAR,
|
|
STRING,
|
|
};
|
|
|
|
// This is part of a special rule that doesn't allow `copts` in Bazel, so we
|
|
// disable warnings using `#pragma`s here.
|
|
#pragma clang diagnostic push
|
|
// The tree_sitter build uses C functions without prototypes.
|
|
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
|
|
|
// our scanner is stateless
|
|
void* tree_sitter_carbon_external_scanner_create(void) { return NULL; }
|
|
|
|
unsigned tree_sitter_carbon_external_scanner_serialize(
|
|
__attribute__((unused)) void* payload,
|
|
__attribute__((unused)) char* buffer) {
|
|
return 0; // zero bytes used to serialize
|
|
}
|
|
|
|
void tree_sitter_carbon_external_scanner_deserialize(
|
|
__attribute__((unused)) void* payload,
|
|
__attribute__((unused)) const char* buffer,
|
|
__attribute__((unused)) unsigned length) {}
|
|
|
|
void tree_sitter_carbon_external_scanner_destroy(
|
|
__attribute__((unused)) void* payload) {}
|
|
|
|
// https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/lexical_conventions/symbolic_tokens.md#overview
|
|
// > the token after the operator must be an identifier, a literal, or any kind
|
|
// of opening bracket (for example, (, [, or {).
|
|
static bool token_allowed_after_binary_operator(char c) {
|
|
return
|
|
// identifier
|
|
c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
|
// string literal
|
|
c == '\"' ||
|
|
// TODO: character literal
|
|
// number literal
|
|
(c >= '0' && c <= '9') ||
|
|
// opening bracket
|
|
c == '(' || c == '[' || c == '{';
|
|
}
|
|
|
|
static bool is_whitespace(char c) { return c == ' ' || c == '\n'; }
|
|
|
|
static void advance(struct TSLexer* lexer) {
|
|
lexer->advance(lexer, /* skip= */ false);
|
|
}
|
|
|
|
static int eat_count(struct TSLexer* lexer, char ch, int count) {
|
|
int matched = 0;
|
|
while (matched != count) {
|
|
if (lexer->lookahead != ch) {
|
|
break;
|
|
}
|
|
advance(lexer);
|
|
matched++;
|
|
}
|
|
return matched;
|
|
}
|
|
|
|
// https://tree-sitter.github.io/tree-sitter/creating-parsers#external-scanners
|
|
//
|
|
// > If a token in the externals array is valid at a given position in the
|
|
// > parse, the external scanner will be called first before anything else is
|
|
// > done.
|
|
//
|
|
// > But the external scanner may return false and in this case Tree-sitter
|
|
// > fallbacks to the internal lexing mechanism.
|
|
bool tree_sitter_carbon_external_scanner_scan(
|
|
__attribute__((unused)) void* payload,
|
|
__attribute__((unused)) TSLexer* lexer, const bool* valid_symbols) {
|
|
// skip past whitespace if any
|
|
bool whitespace = false;
|
|
while (is_whitespace(lexer->lookahead)) {
|
|
whitespace = true;
|
|
lexer->advance(lexer, /* skip= */ true);
|
|
}
|
|
|
|
if (lexer->lookahead == '*' &&
|
|
(valid_symbols[BINARY_STAR] || valid_symbols[POSTFIX_STAR])) {
|
|
// move to past the *, add * to current token
|
|
lexer->advance(lexer, /* skip= */ false);
|
|
|
|
// https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/lexical_conventions/symbolic_tokens.md
|
|
if (is_whitespace(lexer->lookahead) && whitespace) {
|
|
// foo * bar
|
|
lexer->result_symbol = BINARY_STAR;
|
|
} else if (!whitespace &&
|
|
token_allowed_after_binary_operator(lexer->lookahead)) {
|
|
// foo*bar or foo*(bar)
|
|
lexer->result_symbol = BINARY_STAR;
|
|
} else {
|
|
// foo*
|
|
lexer->result_symbol = POSTFIX_STAR;
|
|
}
|
|
return true;
|
|
} else if ((lexer->lookahead == '#' || lexer->lookahead == '\'' ||
|
|
lexer->lookahead == '\"') &&
|
|
valid_symbols[STRING]) {
|
|
lexer->result_symbol = STRING;
|
|
int hash_count = 0;
|
|
while (lexer->lookahead == '#') {
|
|
advance(lexer);
|
|
hash_count++;
|
|
}
|
|
if (lexer->lookahead == '\'') {
|
|
if (eat_count(lexer, '\'', 3) != 3) {
|
|
// lexer state is ignored on return false
|
|
return false;
|
|
}
|
|
while (!lexer->eof(lexer)) {
|
|
// ''' must be on its own line.
|
|
// so look for \n *'''#{hash_count}
|
|
// we can ignore escapes
|
|
if (lexer->lookahead == '\n') {
|
|
advance(lexer);
|
|
while (lexer->lookahead == ' ') {
|
|
advance(lexer);
|
|
}
|
|
if (eat_count(lexer, '\'', 3) != 3) {
|
|
// treat less than 3 `'`s as normal characters
|
|
continue;
|
|
}
|
|
if (eat_count(lexer, '#', hash_count) == hash_count) {
|
|
// end of string
|
|
return true;
|
|
} else {
|
|
// treat as normal characters
|
|
continue;
|
|
}
|
|
} else {
|
|
// skip not new lines
|
|
advance(lexer);
|
|
}
|
|
}
|
|
} else if (lexer->lookahead == '"') {
|
|
advance(lexer);
|
|
while (!lexer->eof(lexer)) {
|
|
if (lexer->lookahead == '\\') {
|
|
advance(lexer);
|
|
if (eat_count(lexer, '#', hash_count) == hash_count) {
|
|
// treat next character as not special
|
|
// but \n is not allowed in simple string
|
|
if (lexer->lookahead != '\n') {
|
|
advance(lexer);
|
|
}
|
|
} else {
|
|
// not an escape
|
|
continue;
|
|
}
|
|
} else if (lexer->lookahead == '"') {
|
|
advance(lexer);
|
|
if (eat_count(lexer, '#', hash_count) == hash_count) {
|
|
// end of string
|
|
return true;
|
|
} else {
|
|
continue;
|
|
}
|
|
} else if (lexer->lookahead == '\n') {
|
|
// new line is not allowed in simple string
|
|
return false;
|
|
} else {
|
|
advance(lexer);
|
|
}
|
|
}
|
|
} else {
|
|
// # not followed by ' or "
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|