mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 20:20:10 +01:00
Makes following changes to Carbon::Format() - TokenKind::Period (i.e. `.`) should never have a space before or after it. - TokenKind::CloseSquareParen (i.e. `]`) should be treated as packed content (no space preceeding it) - Only exception I can think of is `impl forall [...]` - Remove preceeding space from `[` and `(` if previous token was an identifier (or identifier-ish token) - Remove seperator following `++` / `--` unary operators. - Explicit gaps in source code should be retained, up to 2 new lines. Multiple test files were added to test formatting. I imagine eventually this will need to be updated to read parse tree to gather more context but this atleast lets us get a decent-ish format for many of our current sample files (e.g. sieve.carbon) Assisted-With: Gemini / Antigravity --------- Co-authored-by: David Blaikie <dblaikie@gmail.com>
434 lines
16 KiB
C++
434 lines
16 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
|
|
|
|
#ifndef CARBON_TOOLCHAIN_LEX_TOKENIZED_BUFFER_H_
|
|
#define CARBON_TOOLCHAIN_LEX_TOKENIZED_BUFFER_H_
|
|
|
|
#include <cstdint>
|
|
|
|
#include "common/ostream.h"
|
|
#include "llvm/ADT/APInt.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/iterator_range.h"
|
|
#include "llvm/Support/Allocator.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "toolchain/base/index_base.h"
|
|
#include "toolchain/base/mem_usage.h"
|
|
#include "toolchain/base/shared_value_stores.h"
|
|
#include "toolchain/diagnostics/emitter.h"
|
|
#include "toolchain/lex/token_index.h"
|
|
#include "toolchain/lex/token_info.h"
|
|
#include "toolchain/lex/token_kind.h"
|
|
#include "toolchain/source/source_buffer.h"
|
|
|
|
namespace Carbon::Lex {
|
|
|
|
class TokenizedBuffer;
|
|
|
|
struct LineInfo {
|
|
explicit LineInfo(int32_t start) : start(start), indent(0) {}
|
|
|
|
// Zero-based byte offset of the start of the line within the source buffer
|
|
// provided.
|
|
int32_t start;
|
|
|
|
// The byte offset from the start of the line of the first non-whitespace
|
|
// character.
|
|
int32_t indent;
|
|
};
|
|
|
|
// A lightweight handle to a lexed `LineInfo` in a `TokenizedBuffer`.
|
|
//
|
|
// `LineIndex` objects are designed to be passed by value, not reference or
|
|
// pointer. They are also designed to be small and efficient to store in data
|
|
// structures.
|
|
//
|
|
// Each `LineIndex` object refers to a specific line in the source code that was
|
|
// lexed. They can be compared directly to establish that they refer to the
|
|
// same line or the relative position of different lines within the source.
|
|
//
|
|
// All other APIs to query a `LineIndex` are on the `TokenizedBuffer`.
|
|
struct LineIndex : public IndexBase<LineIndex> {
|
|
static constexpr llvm::StringLiteral Label = "line";
|
|
static const LineIndex None;
|
|
using IndexBase::IndexBase;
|
|
};
|
|
|
|
inline constexpr LineIndex LineIndex::None(NoneIndex);
|
|
|
|
// A comment, which can be a block of lines. These are tracked separately from
|
|
// tokens because they don't affect parse; if they were part of tokens, we'd
|
|
// need more general special-casing within token logic.
|
|
//
|
|
// Note that `CommentInfo` is used for an API to expose the comment.
|
|
struct CommentData {
|
|
// Zero-based byte offset of the start of the comment within the source
|
|
// buffer provided.
|
|
int32_t start;
|
|
|
|
// The comment's length, in the low 31 bits. The high bit is stolen for
|
|
// `is_trailing` below to keep `CommentData` at 8 bytes. A byte length is
|
|
// non-negative and the source size is required to be less than `INT32_MAX`
|
|
// (see `Lex`), so the length always fits in 31 bits.
|
|
uint32_t length : 31;
|
|
|
|
// Whether this is a _trailing_ comment: one that follows other content on its
|
|
// line, rather than being the only non-whitespace on the line. Trailing
|
|
// comments are never coalesced with adjacent comments.
|
|
uint32_t is_trailing : 1;
|
|
};
|
|
static_assert(sizeof(CommentData) == 8, "CommentData should pack to 8 bytes");
|
|
|
|
// Indices for `CommentData` within the buffer.
|
|
struct CommentIndex : public IndexBase<CommentIndex> {
|
|
static constexpr llvm::StringLiteral Label = "comment";
|
|
static const CommentIndex None;
|
|
using IndexBase::IndexBase;
|
|
};
|
|
|
|
inline constexpr CommentIndex CommentIndex::None(NoneIndex);
|
|
|
|
// Random-access iterator over comments within the buffer.
|
|
using CommentIterator = IndexIterator<CommentIndex>;
|
|
|
|
// Random-access iterator over tokens within the buffer.
|
|
using TokenIterator = IndexIterator<TokenIndex>;
|
|
|
|
// A token range which is inclusive of the begin and end.
|
|
struct InclusiveTokenRange {
|
|
TokenIndex begin;
|
|
TokenIndex end;
|
|
};
|
|
|
|
// A buffer of tokenized Carbon source code.
|
|
//
|
|
// This is constructed by lexing the source code text into a series of tokens.
|
|
// The buffer provides lightweight handles to tokens and other lexed entities,
|
|
// as well as iterations to walk the sequence of tokens found in the buffer.
|
|
//
|
|
// Lexing errors result in a potentially incomplete sequence of tokens and
|
|
// `HasError` returning true.
|
|
class TokenizedBuffer : public Printable<TokenizedBuffer> {
|
|
public:
|
|
// A comment, which can be a block of lines.
|
|
//
|
|
// This is the API version of `CommentData`.
|
|
struct CommentInfo {
|
|
// The comment's full text, including `//` symbols. This may have several
|
|
// lines for block comments.
|
|
llvm::StringRef text;
|
|
|
|
// The comment's indent.
|
|
int32_t indent;
|
|
|
|
// The first line of the comment.
|
|
LineIndex start_line;
|
|
};
|
|
|
|
auto GetKind(TokenIndex token) const -> TokenKind;
|
|
auto GetLine(TokenIndex token) const -> LineIndex;
|
|
|
|
// Returns the 1-based line number.
|
|
auto GetLineNumber(TokenIndex token) const -> int;
|
|
|
|
// Returns the 1-based column number.
|
|
auto GetColumnNumber(TokenIndex token) const -> int;
|
|
|
|
// Returns the line and 1-based column number of the first character after
|
|
// this token.
|
|
auto GetEndLoc(TokenIndex token) const -> std::pair<LineIndex, int>;
|
|
|
|
// Returns the source text lexed into this token.
|
|
auto GetTokenText(TokenIndex token) const -> llvm::StringRef;
|
|
|
|
// Returns the identifier associated with this token. The token kind must be
|
|
// an `Identifier`.
|
|
auto GetIdentifier(TokenIndex token) const -> IdentifierId;
|
|
|
|
// Returns the value of an `IntLiteral` token.
|
|
auto GetIntLiteral(TokenIndex token) const -> IntId;
|
|
|
|
// Returns the value of an `RealLiteral` token.
|
|
auto GetRealLiteral(TokenIndex token) const -> RealId;
|
|
|
|
// Returns the value of a `StringLiteral` token.
|
|
auto GetStringLiteralValue(TokenIndex token) const -> StringLiteralValueId;
|
|
|
|
// Returns the value of a `CharLiteral` token.
|
|
auto GetCharLiteralValue(TokenIndex token) const -> CharLiteralValue;
|
|
|
|
// Returns the size specified in a `*TypeLiteral` token.
|
|
auto GetTypeLiteralSize(TokenIndex token) const -> IntId;
|
|
|
|
// Returns the closing token matched with the given opening token.
|
|
//
|
|
// The given token must be an opening token kind.
|
|
auto GetMatchedClosingToken(TokenIndex opening_token) const -> TokenIndex;
|
|
|
|
// Returns the opening token matched with the given closing token.
|
|
//
|
|
// The given token must be a closing token kind.
|
|
auto GetMatchedOpeningToken(TokenIndex closing_token) const -> TokenIndex;
|
|
|
|
// Returns whether the given token has leading whitespace.
|
|
auto HasLeadingWhitespace(TokenIndex token) const -> bool;
|
|
// Returns whether the given token has trailing whitespace.
|
|
auto HasTrailingWhitespace(TokenIndex token) const -> bool;
|
|
|
|
// Returns whether the token was spelled as a raw identifier.
|
|
auto IsRawIdentifier(TokenIndex token) const -> bool;
|
|
|
|
// Returns whether the token was created as part of an error recovery effort.
|
|
//
|
|
// For example, a closing paren inserted to match an unmatched paren.
|
|
auto IsRecoveryToken(TokenIndex token) const -> bool;
|
|
|
|
// Adds and returns the index of an identifier token formed from the spelling
|
|
// of the given word token. This should only be used for error recovery
|
|
// purposes, when a phase after the lexer determines that a word token was
|
|
// intended to represent an identifier rather than its special meaning.
|
|
auto AddPostLexingRecoveryTokenAsIdentifier(TokenIndex token) -> TokenIndex;
|
|
|
|
// Returns the 1-based indentation column number.
|
|
auto GetIndentColumnNumber(LineIndex line) const -> int;
|
|
|
|
auto GetByteOffset(TokenIndex token) const -> int32_t {
|
|
return token_infos_.Get(token).byte_offset();
|
|
}
|
|
|
|
// Returns true if the token comes after the comment.
|
|
auto IsAfterComment(TokenIndex token, CommentIndex comment_index) const
|
|
-> bool;
|
|
|
|
// Returns the comment's full text range.
|
|
auto GetCommentText(CommentIndex comment_index) const -> llvm::StringRef;
|
|
|
|
// Returns whether the comment is a trailing comment: one that follows other
|
|
// content on its line.
|
|
auto IsTrailingComment(CommentIndex comment_index) const -> bool;
|
|
|
|
// Returns the line the comment begins on.
|
|
auto GetLine(CommentIndex comment) const -> LineIndex;
|
|
|
|
// Returns tokens as YAML. This prints the tracked token information on a
|
|
// single line for each token. We use the single-line format so that output is
|
|
// compact, and so that tools like `grep` are compatible.
|
|
//
|
|
// An example token looks like:
|
|
//
|
|
// - { index: 1, kind: 'Semi', line: 1, column: 1, indent: 1, spelling: ';' }
|
|
auto Print(llvm::raw_ostream& out,
|
|
bool omit_file_boundary_tokens = false) const -> void;
|
|
|
|
// Prints a description of a single token. See `Print` for details on the
|
|
// format.
|
|
auto PrintToken(llvm::raw_ostream& output_stream, TokenIndex token) const
|
|
-> void;
|
|
|
|
// Collects memory usage of members.
|
|
auto CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const
|
|
-> void;
|
|
|
|
// Converts a token to a diagnostic location.
|
|
auto TokenToDiagnosticLoc(TokenIndex token) const
|
|
-> Diagnostics::ConvertedLoc;
|
|
|
|
// Returns true if the given range overlaps with an entry in
|
|
// `dump_sem_ir_ranges_`. Must not be called when there are no ranges; query
|
|
// `has_dump_sem_ir_ranges` first.
|
|
auto OverlapsWithDumpSemIRRange(Lex::InclusiveTokenRange range) const -> bool;
|
|
|
|
// Returns true if the buffer has errors that were detected at lexing time.
|
|
auto has_errors() const -> bool { return has_errors_; }
|
|
|
|
// Returns the tokens produced by lexing the source file. This includes any
|
|
// recovery tokens inserted inline by lexing, such as matching brackets, but
|
|
// excludes recovery tokens added after lexing finished.
|
|
auto tokens() const -> llvm::iterator_range<TokenIterator> {
|
|
return llvm::make_range(
|
|
TokenIterator(TokenIndex(0)),
|
|
TokenIterator(
|
|
TokenIndex(token_infos_.size() - post_lexing_recovery_tokens_)));
|
|
}
|
|
|
|
auto size() const -> int { return token_infos_.size(); }
|
|
|
|
auto comments() const -> llvm::iterator_range<CommentIterator> {
|
|
return llvm::make_range(CommentIterator(CommentIndex(0)),
|
|
CommentIterator(CommentIndex(comments_.size())));
|
|
}
|
|
|
|
auto comments_size() const -> size_t { return comments_.size(); }
|
|
|
|
auto has_include_in_dumps() const -> bool { return has_include_in_dumps_; }
|
|
|
|
// Returns true if any `DumpSemIRRange`s were provided.
|
|
auto has_dump_sem_ir_ranges() const -> bool {
|
|
return !dump_sem_ir_ranges_.empty();
|
|
}
|
|
|
|
// This is an upper bound on the number of output parse nodes in the absence
|
|
// of errors.
|
|
auto expected_max_parse_tree_size() const -> int {
|
|
return expected_max_parse_tree_size_;
|
|
}
|
|
|
|
auto source() const -> const SourceBuffer& { return *source_; }
|
|
|
|
private:
|
|
friend class Lexer;
|
|
|
|
class SourcePointerDiagnosticEmitter
|
|
: public Diagnostics::Emitter<const char*> {
|
|
public:
|
|
explicit SourcePointerDiagnosticEmitter(Diagnostics::Consumer* consumer,
|
|
const TokenizedBuffer* tokens)
|
|
: Emitter(consumer), tokens_(tokens) {}
|
|
|
|
protected:
|
|
auto ConvertLoc(const char* loc, ContextFnT /*context_fn*/) const
|
|
-> Diagnostics::ConvertedLoc override {
|
|
return tokens_->SourcePointerToDiagnosticLoc(loc);
|
|
}
|
|
|
|
private:
|
|
const TokenizedBuffer* tokens_;
|
|
};
|
|
|
|
class TokenDiagnosticEmitter : public Diagnostics::Emitter<TokenIndex> {
|
|
public:
|
|
explicit TokenDiagnosticEmitter(Diagnostics::Consumer* consumer,
|
|
const TokenizedBuffer* tokens)
|
|
: Emitter(consumer), tokens_(tokens) {}
|
|
|
|
protected:
|
|
auto ConvertLoc(TokenIndex token, ContextFnT /*context_fn*/) const
|
|
-> Diagnostics::ConvertedLoc override {
|
|
return tokens_->TokenToDiagnosticLoc(token);
|
|
}
|
|
|
|
private:
|
|
const TokenizedBuffer* tokens_;
|
|
};
|
|
|
|
// Converts a pointer into the source to a diagnostic location.
|
|
auto SourcePointerToDiagnosticLoc(const char* loc) const
|
|
-> Diagnostics::ConvertedLoc;
|
|
|
|
// Specifies minimum widths to use when printing a token's fields via
|
|
// `printToken`.
|
|
struct PrintWidths {
|
|
// Widens `this` to the maximum of `this` and `new_width` for each
|
|
// dimension.
|
|
auto Widen(const PrintWidths& widths) -> void;
|
|
|
|
int index;
|
|
int kind;
|
|
int line;
|
|
int column;
|
|
int indent;
|
|
};
|
|
|
|
// The constructor is merely responsible for trivial initialization of
|
|
// members. A working object of this type is built with `Lex::Lex` so that its
|
|
// return can indicate if an error was encountered while lexing.
|
|
explicit TokenizedBuffer(SharedValueStores& value_stores
|
|
[[clang::lifetimebound]],
|
|
SourceBuffer& source [[clang::lifetimebound]])
|
|
: value_stores_(&value_stores), source_(&source) {}
|
|
|
|
auto FindLineIndex(int32_t byte_offset) const -> LineIndex;
|
|
|
|
// Adds the token and adjusts the expected tree size.
|
|
auto AddToken(TokenInfo info) -> TokenIndex;
|
|
|
|
// Adds a post-lexing recovery token and adjusts the buffer state.
|
|
auto AddPostLexingRecoveryToken(TokenInfo info) -> TokenIndex;
|
|
|
|
auto GetTokenPrintWidths(TokenIndex token) const -> PrintWidths;
|
|
auto PrintToken(llvm::raw_ostream& output_stream, TokenIndex token,
|
|
PrintWidths widths) const -> void;
|
|
|
|
// Adds a comment. For full-line comments (`is_trailing` is false), this uses
|
|
// the indent to potentially stitch together two adjacent comments into a
|
|
// single block. Trailing comments are never stitched, either onto a preceding
|
|
// comment or as the target of a following one.
|
|
auto AddComment(int32_t indent, int32_t start, int32_t end, bool is_trailing)
|
|
-> void;
|
|
|
|
// Used to allocate computed string literals.
|
|
llvm::BumpPtrAllocator allocator_;
|
|
|
|
SharedValueStores* value_stores_;
|
|
SourceBuffer* source_;
|
|
|
|
ValueStore<TokenIndex, TokenInfo> token_infos_;
|
|
|
|
ValueStore<LineIndex, LineInfo> line_infos_;
|
|
|
|
// Comments in the file.
|
|
ValueStore<CommentIndex, CommentData> comments_;
|
|
|
|
// Whether SemIR dumping is explicitly enabled for this file. This is marked
|
|
// by `//@include-in-dumps`, and overrides other file-inclusion selection
|
|
// choices. It can be combined with ranges.
|
|
bool has_include_in_dumps_ = false;
|
|
|
|
// A range of tokens marked by `//@dump-sem-ir-[begin|end]`.
|
|
//
|
|
// The particular syntax was chosen because it can be lexed efficiently. It
|
|
// only occurs in invalid comment strings, so shouldn't slow down lexing of
|
|
// correct code. It's also comment-like because its presence won't affect
|
|
// parse/check.
|
|
llvm::SmallVector<InclusiveTokenRange> dump_sem_ir_ranges_;
|
|
|
|
// An upper bound on the number of parse tree nodes that we expect to be
|
|
// created for the tokens in this buffer.
|
|
int expected_max_parse_tree_size_ = 0;
|
|
|
|
bool has_errors_ = false;
|
|
|
|
// The number of recovery tokens created after lexing finished. These tokens
|
|
// are included in `token_infos_`, but are excluded from the token sequence
|
|
// produced by `tokens()`.
|
|
int32_t post_lexing_recovery_tokens_ = 0;
|
|
|
|
// A vector of flags for recovery tokens. If empty, there are none. When doing
|
|
// token recovery, this will be extended to be indexable by token indices and
|
|
// contain true for the tokens that were synthesized for recovery.
|
|
llvm::BitVector recovery_tokens_;
|
|
};
|
|
|
|
inline auto TokenizedBuffer::GetKind(TokenIndex token) const -> TokenKind {
|
|
return token_infos_.Get(token).kind();
|
|
}
|
|
|
|
inline auto TokenizedBuffer::HasLeadingWhitespace(TokenIndex token) const
|
|
-> bool {
|
|
return token_infos_.Get(token).has_leading_space();
|
|
}
|
|
|
|
inline auto TokenizedBuffer::HasTrailingWhitespace(TokenIndex token) const
|
|
-> bool {
|
|
TokenIterator it(token);
|
|
++it;
|
|
return it != tokens().end() && token_infos_.Get(*it).has_leading_space();
|
|
}
|
|
|
|
inline auto TokenizedBuffer::AddToken(TokenInfo info) -> TokenIndex {
|
|
expected_max_parse_tree_size_ += info.kind().expected_max_parse_tree_size();
|
|
return token_infos_.Add(info);
|
|
}
|
|
|
|
} // namespace Carbon::Lex
|
|
|
|
namespace Carbon {
|
|
extern template class ValueStore<Lex::TokenIndex, Lex::TokenInfo>;
|
|
extern template class ValueStore<Lex::LineIndex, Lex::LineInfo>;
|
|
extern template class ValueStore<Lex::CommentIndex, Lex::CommentData>;
|
|
} // namespace Carbon
|
|
|
|
#endif // CARBON_TOOLCHAIN_LEX_TOKENIZED_BUFFER_H_
|