| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tools/fidl/fidlc/src/parser.h" |
| |
| #include <errno.h> |
| #include <lib/fit/function.h> |
| #include <zircon/assert.h> |
| |
| #include "tools/fidl/fidlc/src/diagnostics.h" |
| #include "tools/fidl/fidlc/src/experimental_flags.h" |
| #include "tools/fidl/fidlc/src/properties.h" |
| #include "tools/fidl/fidlc/src/raw_ast.h" |
| #include "tools/fidl/fidlc/src/token.h" |
| #include "tools/fidl/fidlc/src/utils.h" |
| |
| namespace fidlc { |
| |
| // The "case" keyword is not folded into CASE_TOKEN and CASE_IDENTIFIER because |
| // doing so confuses clang-format. |
| #define CASE_TOKEN(K) Token::KindAndSubkind(K, Token::Subkind::kNone).combined() |
| |
| #define CASE_IDENTIFIER(K) Token::KindAndSubkind(Token::Kind::kIdentifier, K).combined() |
| |
| #define TOKEN_LITERAL_CASES \ |
| case CASE_IDENTIFIER(Token::Subkind::kTrue): \ |
| case CASE_IDENTIFIER(Token::Subkind::kFalse): \ |
| case CASE_TOKEN(Token::Kind::kNumericLiteral): \ |
| case CASE_TOKEN(Token::Kind::kStringLiteral) |
| |
| namespace { |
| |
| enum { |
| More, |
| Done, |
| }; |
| |
| template <typename T, typename Fn> |
| void add(std::vector<std::unique_ptr<T>>* elements, Fn producer_fn) { |
| fit::function<std::unique_ptr<T>()> producer(producer_fn); |
| auto element = producer(); |
| if (element) |
| elements->emplace_back(std::move(element)); |
| } |
| |
| } // namespace |
| |
| Parser::Parser(Lexer* lexer, Reporter* reporter, ExperimentalFlagSet experimental_flags) |
| : lexer_(lexer), |
| reporter_(reporter), |
| checkpoint_(reporter->Checkpoint()), |
| experimental_flags_(experimental_flags), |
| state_(State::kNormal) { |
| last_token_ = Lex(); |
| } |
| |
| std::nullptr_t Parser::Fail() { return Fail(ErrUnexpectedToken); } |
| |
| template <ErrorId Id, typename... Args> |
| std::nullptr_t Parser::Fail(const ErrorDef<Id, Args...>& err, const identity_t<Args>&... args) { |
| return Fail(err, last_token_, args...); |
| } |
| |
| template <ErrorId Id, typename... Args> |
| std::nullptr_t Parser::Fail(const ErrorDef<Id, Args...>& err, const Token token, |
| const identity_t<Args>&... args) { |
| return Fail(err, token.span(), args...); |
| } |
| |
| template <ErrorId Id, typename... Args> |
| std::nullptr_t Parser::Fail(const ErrorDef<Id, Args...>& err, SourceSpan span, |
| const identity_t<Args>&... args) { |
| if (Ok()) { |
| reporter_->Fail(err, span, args...); |
| } |
| return nullptr; |
| } |
| |
| std::unique_ptr<RawIdentifier> Parser::ParseIdentifier() { |
| ASTScope scope(this); |
| std::optional<Token> token = ConsumeToken(OfKind(Token::Kind::kIdentifier)); |
| if (!Ok() || !token) |
| return Fail(); |
| std::string identifier(token->data()); |
| if (!IsValidIdentifierComponent(identifier)) |
| return Fail(ErrInvalidIdentifier, identifier); |
| |
| return std::make_unique<RawIdentifier>(scope.GetSourceElement()); |
| } |
| |
| std::unique_ptr<RawCompoundIdentifier> Parser::ParseCompoundIdentifier() { |
| ASTScope scope(this); |
| auto first_identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| return ParseCompoundIdentifier(scope, std::move(first_identifier)); |
| } |
| |
| std::unique_ptr<RawCompoundIdentifier> Parser::ParseCompoundIdentifier( |
| ASTScope& scope, std::unique_ptr<RawIdentifier> first_identifier) { |
| std::vector<std::unique_ptr<RawIdentifier>> components; |
| components.push_back(std::move(first_identifier)); |
| |
| auto parse_component = [&components, this]() { |
| switch (Peek().combined()) { |
| default: |
| return Done; |
| |
| case CASE_TOKEN(Token::Kind::kDot): |
| ConsumeToken(OfKind(Token::Kind::kDot)); |
| if (Ok()) { |
| components.emplace_back(ParseIdentifier()); |
| } |
| return More; |
| } |
| }; |
| |
| while (parse_component() == More) { |
| if (!Ok()) |
| return Fail(); |
| } |
| |
| return std::make_unique<RawCompoundIdentifier>(scope.GetSourceElement(), std::move(components)); |
| } |
| |
| std::unique_ptr<RawLibraryDeclaration> Parser::ParseLibraryDeclaration() { |
| ASTScope scope(this); |
| auto attributes = MaybeParseAttributeList(); |
| if (!Ok()) |
| return Fail(); |
| |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kLibrary)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto library_name = ParseCompoundIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| for (const auto& component : library_name->components) { |
| std::string component_data(component->start_token.data()); |
| if (!IsValidLibraryComponent(component_data)) { |
| return Fail(ErrInvalidLibraryNameComponent, component->start_token, component_data); |
| } |
| } |
| |
| return std::make_unique<RawLibraryDeclaration>(scope.GetSourceElement(), std::move(attributes), |
| std::move(library_name)); |
| } |
| |
| std::unique_ptr<RawStringLiteral> Parser::ParseStringLiteral() { |
| ASTScope scope(this); |
| ConsumeToken(OfKind(Token::Kind::kStringLiteral)); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawStringLiteral>(scope.GetSourceElement()); |
| } |
| |
| std::unique_ptr<RawNumericLiteral> Parser::ParseNumericLiteral() { |
| ASTScope scope(this); |
| ConsumeToken(OfKind(Token::Kind::kNumericLiteral)); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawNumericLiteral>(scope.GetSourceElement()); |
| } |
| |
| std::unique_ptr<RawOrdinal64> Parser::ParseOrdinal64() { |
| ASTScope scope(this); |
| |
| if (!MaybeConsumeToken(OfKind(Token::Kind::kNumericLiteral))) |
| return Fail(ErrMissingOrdinalBeforeMember); |
| if (!Ok()) |
| return Fail(); |
| auto data = scope.GetSourceElement().span().data(); |
| std::string string_data(data.data(), data.data() + data.size()); |
| errno = 0; |
| unsigned long long value = strtoull(string_data.data(), nullptr, 0); |
| ZX_ASSERT_MSG(errno == 0, "unparsable number should not be lexed."); |
| if (value > std::numeric_limits<uint32_t>::max()) |
| return Fail(ErrOrdinalOutOfBound); |
| uint32_t ordinal = static_cast<uint32_t>(value); |
| if (ordinal == 0u) |
| return Fail(ErrOrdinalsMustStartAtOne); |
| |
| ConsumeToken(OfKind(Token::Kind::kColon)); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawOrdinal64>(scope.GetSourceElement(), ordinal); |
| } |
| |
| std::unique_ptr<RawBoolLiteral> Parser::ParseBoolLiteral(Token::Subkind subkind) { |
| ASTScope scope(this); |
| ConsumeToken(IdentifierOfSubkind(subkind)); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawBoolLiteral>(scope.GetSourceElement(), |
| Token::Subkind::kTrue == subkind); |
| } |
| |
| std::unique_ptr<RawLiteral> Parser::ParseLiteral() { |
| switch (Peek().combined()) { |
| case CASE_TOKEN(Token::Kind::kStringLiteral): |
| return ParseStringLiteral(); |
| |
| case CASE_TOKEN(Token::Kind::kNumericLiteral): |
| return ParseNumericLiteral(); |
| |
| case CASE_IDENTIFIER(Token::Subkind::kTrue): |
| return ParseBoolLiteral(Token::Subkind::kTrue); |
| |
| case CASE_IDENTIFIER(Token::Subkind::kFalse): |
| return ParseBoolLiteral(Token::Subkind::kFalse); |
| |
| default: |
| return Fail(); |
| } |
| } |
| |
| std::unique_ptr<RawAttributeArg> Parser::ParseSubsequentAttributeArg() { |
| ASTScope scope(this); |
| auto name = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| ConsumeToken(OfKind(Token::Kind::kEqual)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto value = ParseConstant(); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawAttributeArg>(scope.GetSourceElement(), std::move(name), |
| std::move(value)); |
| } |
| |
| std::unique_ptr<RawAttribute> Parser::ParseAttribute() { |
| ASTScope scope(this); |
| ConsumeToken(OfKind(Token::Kind::kAt)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto name = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| std::vector<std::unique_ptr<RawAttributeArg>> args; |
| if (MaybeConsumeToken(OfKind(Token::Kind::kLeftParen))) { |
| if (Peek().kind() == Token::Kind::kRightParen) { |
| return Fail(ErrAttributeWithEmptyParens); |
| } |
| |
| // There are two valid syntaxes for attribute arg lists: single arg lists contain just the |
| // arg constant by itself, like so: |
| // |
| // @foo("bar") // Literal constant |
| // @baz(qux) // Identifier constant |
| // |
| // Conversely, multi-argument lists must name each argument, like so: |
| // |
| // @foo(a="bar",b=qux) |
| // |
| // To resolve this ambiguity, we will speculatively parse the first token encountered as a |
| // constant. If it is followed by a close paren, we know that we are in the single-arg case, |
| // and that this parsing is correct. If is instead followed by an equal sign, we know that this |
| // is the multi-arg case, and we will extract the identifier from the constant to be used as the |
| // name token for the first arg in the list. |
| ASTScope arg_scope(this); |
| auto maybe_constant = ParseConstant(); |
| if (!Ok()) |
| return Fail(); |
| |
| switch (Peek().kind()) { |
| case Token::Kind::kRightParen: { |
| // This attribute has a single, unnamed argument. |
| args.emplace_back(std::make_unique<RawAttributeArg>(arg_scope.GetSourceElement(), |
| std::move(maybe_constant))); |
| ConsumeToken(OfKind(Token::Kind::kRightParen)); |
| if (!Ok()) |
| return Fail(); |
| break; |
| } |
| case Token::Kind::kComma: { |
| // Common error case: multiple arguments, but the first one is not named |
| return Fail(ErrAttributeArgsMustAllBeNamed); |
| } |
| case Token::Kind::kEqual: { |
| // This attribute has multiple arguments. |
| if (maybe_constant->kind != RawConstant::Kind::kIdentifier) { |
| return Fail(ErrInvalidIdentifier, maybe_constant->span().data()); |
| } |
| auto constant = static_cast<RawIdentifierConstant*>(maybe_constant.get()); |
| if (constant->identifier->components.size() > 1) { |
| return Fail(ErrInvalidIdentifier, maybe_constant->span().data()); |
| } |
| |
| ConsumeToken(OfKind(Token::Kind::kEqual)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto arg_name = std::move(constant->identifier->components.front()); |
| auto value = ParseConstant(); |
| if (!Ok()) |
| return Fail(); |
| |
| args.emplace_back(std::make_unique<RawAttributeArg>(arg_scope.GetSourceElement(), |
| std::move(arg_name), std::move(value))); |
| while (Peek().kind() == Token::Kind::kComma) { |
| ConsumeToken(OfKind(Token::Kind::kComma)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto arg = ParseSubsequentAttributeArg(); |
| if (!Ok()) { |
| const auto result = RecoverToEndOfAttributeArg(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| } |
| args.emplace_back(std::move(arg)); |
| } |
| if (!Ok()) |
| Fail(); |
| |
| ConsumeToken(OfKind(Token::Kind::kRightParen)); |
| if (!Ok()) |
| return Fail(); |
| break; |
| } |
| default: |
| return Fail(); |
| } |
| } |
| |
| return std::make_unique<RawAttribute>(scope.GetSourceElement(), std::move(name), std::move(args)); |
| } |
| |
| std::unique_ptr<RawAttributeList> Parser::ParseAttributeList( |
| std::unique_ptr<RawAttribute> doc_comment, ASTScope& scope) { |
| std::vector<std::unique_ptr<RawAttribute>> attributes; |
| if (doc_comment) |
| attributes.emplace_back(std::move(doc_comment)); |
| |
| for (;;) { |
| auto attribute = ParseAttribute(); |
| if (!Ok()) { |
| auto result = RecoverToEndOfAttributeNew(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| if (result == RecoverResult::EndOfScope) { |
| break; |
| } |
| } else { |
| attributes.emplace_back(std::move(attribute)); |
| } |
| |
| if (Peek().kind() != Token::Kind::kAt) { |
| break; |
| } |
| } |
| |
| return std::make_unique<RawAttributeList>(scope.GetSourceElement(), std::move(attributes)); |
| } |
| |
| std::unique_ptr<RawAttribute> Parser::ParseDocComment() { |
| ASTScope scope(this); |
| std::optional<Token> doc_line; |
| std::optional<Token> first_doc_line; |
| while (Peek().kind() == Token::Kind::kDocComment) { |
| if (first_doc_line) { |
| // Disallow any blank lines between this doc comment and the previous one. |
| if (last_token_.leading_newlines() > 1) |
| reporter_->Warn(WarnBlankLinesWithinDocCommentBlock, previous_token_.span()); |
| } |
| |
| doc_line = ConsumeToken(OfKind(Token::Kind::kDocComment)); |
| if (!Ok() || !doc_line) |
| return Fail(); |
| if (!first_doc_line) { |
| first_doc_line = doc_line; |
| } |
| } |
| |
| auto literal = std::make_unique<RawDocCommentLiteral>(scope.GetSourceElement()); |
| auto constant = std::make_unique<RawLiteralConstant>(std::move(literal)); |
| if (Peek().kind() == Token::Kind::kEndOfFile) |
| reporter_->Warn(WarnDocCommentMustBeFollowedByDeclaration, previous_token_.span()); |
| |
| std::vector<std::unique_ptr<RawAttributeArg>> args; |
| args.emplace_back( |
| std::make_unique<RawAttributeArg>(scope.GetSourceElement(), std::move(constant))); |
| |
| auto doc_comment_attr = RawAttribute::CreateDocComment(scope.GetSourceElement(), std::move(args)); |
| return std::make_unique<RawAttribute>(std::move(doc_comment_attr)); |
| } |
| |
| std::unique_ptr<RawAttributeList> Parser::MaybeParseAttributeList() { |
| ASTScope scope(this); |
| std::unique_ptr<RawAttribute> doc_comment; |
| // Doc comments must appear above attributes |
| if (Peek().kind() == Token::Kind::kDocComment) { |
| doc_comment = ParseDocComment(); |
| } |
| if (Peek().kind() == Token::Kind::kAt) { |
| return ParseAttributeList(std::move(doc_comment), scope); |
| } |
| // no generic attributes, start the attribute list |
| if (doc_comment) { |
| std::vector<std::unique_ptr<RawAttribute>> attributes; |
| attributes.emplace_back(std::move(doc_comment)); |
| return std::make_unique<RawAttributeList>(scope.GetSourceElement(), std::move(attributes)); |
| } |
| return nullptr; |
| } |
| |
| std::unique_ptr<RawConstant> Parser::ParseConstant() { |
| std::unique_ptr<RawConstant> constant; |
| |
| switch (Peek().combined()) { |
| // TODO(https://fxbug.dev/42157590): by placing this before the kIdentifier check below, we are |
| // implicitly |
| // stating that the tokens "true" and "false" will always be interpreted as their literal |
| // constants. Consider the following example: |
| // const true string = "abc"; |
| // const foo bool = false; // "false" retains its built-in literal value, so no problem |
| // const bar bool = true; // "true" has been redefined as a string type - should this fail? |
| // We could maintain perfect purity by always treating all tokens, even "true" and "false," as |
| // identifier (rather than literal) constants, meaning that we would never be able to parse a |
| // Token::Subkind::True|False. Since letting people overwrite the value of true and false is |
| // undesirable for usability (and sanity) reasons, we should instead modify the compiler to |
| // specifically catch `const true|false ...` cases, and show a "don't change the meaning of |
| // true and false please" error instead. |
| TOKEN_LITERAL_CASES: { |
| auto literal = ParseLiteral(); |
| if (!Ok()) |
| return Fail(); |
| constant = std::make_unique<RawLiteralConstant>(std::move(literal)); |
| break; |
| } |
| |
| case CASE_TOKEN(Token::Kind::kLeftParen): { |
| ASTScope scope(this); |
| ConsumeToken(OfKind(Token::Kind::kLeftParen)); |
| constant = ParseConstant(); |
| ConsumeToken(OfKind(Token::Kind::kRightParen)); |
| if (!Ok()) |
| return Fail(); |
| break; |
| } |
| |
| default: { |
| if (Peek().kind() == Token::Kind::kIdentifier) { |
| auto identifier = ParseCompoundIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| constant = std::make_unique<RawIdentifierConstant>(std::move(identifier)); |
| } else { |
| return Fail(); |
| } |
| } |
| } |
| |
| if (Peek().combined() == Token::Kind::kPipe) { |
| ConsumeToken(OfKind(Token::Kind::kPipe)); |
| std::unique_ptr right_operand = ParseConstant(); |
| if (!Ok()) |
| return Fail(); |
| return std::make_unique<RawBinaryOperatorConstant>( |
| std::move(constant), std::move(right_operand), RawBinaryOperatorConstant::Operator::kOr); |
| } |
| return constant; |
| } |
| |
| std::unique_ptr<RawAliasDeclaration> Parser::ParseAliasDeclaration( |
| std::unique_ptr<RawAttributeList> attributes, ASTScope& scope) { |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kAlias)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto alias = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| ConsumeToken(OfKind(Token::Kind::kEqual)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto type_ctor = ParseTypeConstructor(); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawAliasDeclaration>(scope.GetSourceElement(), std::move(attributes), |
| std::move(alias), std::move(type_ctor)); |
| } |
| |
| std::unique_ptr<RawUsing> Parser::ParseUsing(std::unique_ptr<RawAttributeList> attributes, |
| ASTScope& scope) { |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kUsing)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto using_path = ParseCompoundIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| std::unique_ptr<RawIdentifier> maybe_alias; |
| if (MaybeConsumeToken(IdentifierOfSubkind(Token::Subkind::kAs))) { |
| if (!Ok()) |
| return Fail(); |
| maybe_alias = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| } |
| |
| return std::make_unique<RawUsing>(scope.GetSourceElement(), std::move(attributes), |
| std::move(using_path), std::move(maybe_alias)); |
| } |
| |
| std::unique_ptr<RawConstDeclaration> Parser::ParseConstDeclaration( |
| std::unique_ptr<RawAttributeList> attributes, ASTScope& scope) { |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kConst)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| auto type_ctor = ParseTypeConstructor(); |
| if (!Ok()) |
| return Fail(); |
| |
| ConsumeToken(OfKind(Token::Kind::kEqual)); |
| if (!Ok()) |
| return Fail(); |
| auto constant = ParseConstant(); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawConstDeclaration>(scope.GetSourceElement(), std::move(attributes), |
| std::move(type_ctor), std::move(identifier), |
| std::move(constant)); |
| } |
| |
| std::unique_ptr<RawParameterList> Parser::ParseParameterList() { |
| ASTScope scope(this); |
| std::unique_ptr<RawTypeConstructor> type_ctor; |
| |
| ConsumeToken(OfKind(Token::Kind::kLeftParen)); |
| if (!Ok()) |
| return Fail(); |
| |
| if (Peek().kind() != Token::Kind::kRightParen) { |
| type_ctor = ParseTypeConstructor(); |
| if (!Ok()) { |
| const auto result = RecoverToEndOfParamList(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| } |
| |
| if (type_ctor && type_ctor->layout_ref->kind == RawLayoutReference::Kind::kInline) { |
| const auto* layout = |
| static_cast<const RawInlineLayoutReference*>(type_ctor->layout_ref.get()); |
| if (layout->attributes != nullptr) { |
| auto& attrs = layout->attributes->attributes; |
| if (!attrs.empty() && attrs[0]->provenance == RawAttribute::Provenance::kDocComment) { |
| auto& args = attrs[0]->args; |
| if (!args.empty() && args[0]->value->kind == RawConstant::Kind::kLiteral) { |
| auto literal_constant = static_cast<RawLiteralConstant*>(args[0]->value.get()); |
| if (literal_constant->literal->kind == RawLiteral::Kind::kDocComment) { |
| Fail(ErrDocCommentOnParameters, attrs[0]->span()); |
| const auto result = RecoverToEndOfParamList(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| ConsumeToken(OfKind(Token::Kind::kRightParen)); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawParameterList>(scope.GetSourceElement(), std::move(type_ctor)); |
| } |
| |
| std::unique_ptr<RawProtocolMethod> Parser::ParseProtocolEvent( |
| std::unique_ptr<RawAttributeList> attributes, std::unique_ptr<RawModifiers> modifiers, |
| ASTScope& scope) { |
| ConsumeToken(OfKind(Token::Kind::kArrow)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto method_name = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| auto parse_params = [this](std::unique_ptr<RawParameterList>* params_out) { |
| if (!Ok()) |
| return false; |
| *params_out = ParseParameterList(); |
| return Ok(); |
| }; |
| |
| std::unique_ptr<RawParameterList> request; |
| std::unique_ptr<RawParameterList> response; |
| if (!parse_params(&response)) |
| return Fail(); |
| |
| ZX_ASSERT(method_name); |
| ZX_ASSERT(response != nullptr); |
| |
| return std::make_unique<RawProtocolMethod>( |
| scope.GetSourceElement(), std::move(attributes), std::move(modifiers), std::move(method_name), |
| std::move(request), std::move(response), /*maybe_error_ctor=*/nullptr); |
| } |
| |
| std::unique_ptr<RawProtocolMethod> Parser::ParseProtocolMethod( |
| std::unique_ptr<RawAttributeList> attributes, std::unique_ptr<RawModifiers> modifiers, |
| std::unique_ptr<RawIdentifier> method_name, ASTScope& scope) { |
| auto parse_params = [this](std::unique_ptr<RawParameterList>* params_out) { |
| *params_out = ParseParameterList(); |
| return Ok(); |
| }; |
| |
| std::unique_ptr<RawParameterList> request; |
| if (!parse_params(&request)) |
| return Fail(); |
| |
| std::unique_ptr<RawParameterList> maybe_response; |
| std::unique_ptr<RawTypeConstructor> maybe_error; |
| if (MaybeConsumeToken(OfKind(Token::Kind::kArrow))) { |
| if (!Ok()) |
| return Fail(); |
| if (!parse_params(&maybe_response)) |
| return Fail(); |
| if (MaybeConsumeToken(IdentifierOfSubkind(Token::Subkind::kError))) { |
| maybe_error = ParseTypeConstructor(); |
| if (!Ok()) |
| return Fail(); |
| } |
| } |
| |
| ZX_ASSERT(method_name); |
| ZX_ASSERT(request != nullptr); |
| |
| return std::make_unique<RawProtocolMethod>( |
| scope.GetSourceElement(), std::move(attributes), std::move(modifiers), std::move(method_name), |
| std::move(request), std::move(maybe_response), std::move(maybe_error)); |
| } |
| |
| std::unique_ptr<RawProtocolCompose> Parser::ParseProtocolCompose( |
| std::unique_ptr<RawAttributeList> attributes, ASTScope& scope) { |
| auto identifier = ParseCompoundIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawProtocolCompose>(scope.GetSourceElement(), std::move(attributes), |
| std::move(identifier)); |
| } |
| |
| void Parser::ParseProtocolMember( |
| std::vector<std::unique_ptr<RawProtocolCompose>>* composed_protocols, |
| std::vector<std::unique_ptr<RawProtocolMethod>>* methods) { |
| ASTScope scope(this); |
| std::unique_ptr<RawAttributeList> attributes = MaybeParseAttributeList(); |
| if (!Ok()) { |
| Fail(); |
| return; |
| } |
| |
| switch (Peek().kind()) { |
| case Token::Kind::kArrow: { |
| add(methods, [&] { |
| return ParseProtocolEvent(std::move(attributes), /* modifiers= */ nullptr, scope); |
| }); |
| return; |
| } |
| case Token::Kind::kIdentifier: { |
| std::unique_ptr<RawModifiers> modifiers; |
| std::unique_ptr<RawIdentifier> method_name; |
| if (Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kCompose)) { |
| // There are two possibilities here: we are looking at the first token in a compose |
| // statement like `compose a.b;`, or we are looking at the identifier of a method that has |
| // unfortunately been named `compose(...);`. Instead of calling ParseIdentifier here, we |
| // merely consume the token for now. |
| const auto compose_token = ConsumeToken(IdentifierOfSubkind(Token::Subkind::kCompose)); |
| if (!Ok()) { |
| Fail(); |
| return; |
| } |
| |
| // If the `compose` identifier is not immediately followed by a left paren we assume that we |
| // are looking at a compose clause. |
| if (Peek().kind() != Token::Kind::kLeftParen) { |
| add(composed_protocols, |
| [&] { return ParseProtocolCompose(std::move(attributes), scope); }); |
| return; |
| } |
| |
| // Looks like this is a `compose(...);` method after all, so coerce the composed token into |
| // an Identifier source element. |
| method_name = std::make_unique<RawIdentifier>( |
| SourceElement(compose_token.value(), compose_token.value())); |
| } else if (Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kStrict) || |
| Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kFlexible)) { |
| // There are two possibilities here: we are looking at a method or event with strictness |
| // modifier like `strict MyMethod(...);` or we are looking at a method |
| // that has unfortunately been named `flexible/strict(...);`. In either case we only expect |
| // one identifier, not a compound identifier, so we can just parse the identifier. |
| auto modifier_subkind = Peek().subkind(); |
| auto maybe_modifier = ParseIdentifier(); |
| if (!Ok()) { |
| Fail(); |
| return; |
| } |
| |
| if (Peek().kind() == Token::Kind::kLeftParen) { |
| // This is actually a method named `strict` or `flexible`. |
| method_name = std::move(maybe_modifier); |
| } else { |
| // This is a modifier on either an event or a method. |
| auto as_strictness = modifier_subkind == Token::Subkind::kFlexible ? Strictness::kFlexible |
| : Strictness::kStrict; |
| modifiers = std::make_unique<RawModifiers>( |
| SourceElement(maybe_modifier->start_token, maybe_modifier->end_token), |
| RawModifier<Strictness>(as_strictness, maybe_modifier->start_token)); |
| switch (Peek().kind()) { |
| case Token::Kind::kArrow: { |
| add(methods, [&] { |
| return ParseProtocolEvent(std::move(attributes), std::move(modifiers), scope); |
| }); |
| return; |
| } |
| case Token::Kind::kIdentifier: { |
| method_name = ParseIdentifier(); |
| if (!Ok()) { |
| Fail(); |
| return; |
| } |
| if (Peek().kind() != Token::Kind::kLeftParen) { |
| Fail(ErrInvalidProtocolMember); |
| return; |
| } |
| break; |
| } |
| default: |
| Fail(ErrInvalidProtocolMember); |
| return; |
| } |
| } |
| } else { |
| method_name = ParseIdentifier(); |
| if (!Ok()) { |
| Fail(); |
| return; |
| } |
| if (Peek().kind() != Token::Kind::kLeftParen) { |
| Fail(ErrInvalidProtocolMember); |
| return; |
| } |
| } |
| |
| add(methods, [&] { |
| return ParseProtocolMethod(std::move(attributes), std::move(modifiers), |
| std::move(method_name), scope); |
| }); |
| return; |
| } |
| default: |
| Fail(ErrInvalidProtocolMember); |
| return; |
| } |
| } |
| |
| std::unique_ptr<RawProtocolDeclaration> Parser::ParseProtocolDeclaration( |
| std::unique_ptr<RawAttributeList> attributes, ASTScope& scope) { |
| std::unique_ptr<RawModifiers> modifiers; |
| std::vector<std::unique_ptr<RawProtocolCompose>> composed_protocols; |
| std::vector<std::unique_ptr<RawProtocolMethod>> methods; |
| |
| if (Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kOpen) || |
| Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kAjar) || |
| Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kClosed)) { |
| auto modifier_subkind = Peek().subkind(); |
| auto modifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| Openness as_openness; |
| switch (modifier_subkind) { |
| case Token::Subkind::kOpen: |
| as_openness = Openness::kOpen; |
| break; |
| case Token::Subkind::kAjar: |
| as_openness = Openness::kAjar; |
| break; |
| case Token::Subkind::kClosed: |
| as_openness = Openness::kClosed; |
| break; |
| default: |
| ZX_PANIC("expected openness token"); |
| } |
| modifiers = |
| std::make_unique<RawModifiers>(SourceElement(modifier->start_token, modifier->end_token), |
| RawModifier<Openness>(as_openness, modifier->start_token)); |
| } |
| |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kProtocol)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| ConsumeToken(OfKind(Token::Kind::kLeftCurly)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto parse_member = [&composed_protocols, &methods, this]() { |
| if (Peek().kind() == Token::Kind::kRightCurly) { |
| ConsumeToken(OfKind(Token::Kind::kRightCurly)); |
| return Done; |
| } |
| ParseProtocolMember(&composed_protocols, &methods); |
| return More; |
| }; |
| |
| while (parse_member() == More) { |
| if (!Ok()) { |
| const auto result = RecoverToEndOfMember(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| if (result == RecoverResult::EndOfScope) { |
| continue; |
| } |
| } |
| ConsumeTokenOrRecover(OfKind(Token::Kind::kSemicolon)); |
| } |
| if (!Ok()) |
| Fail(); |
| |
| return std::make_unique<RawProtocolDeclaration>( |
| scope.GetSourceElement(), std::move(attributes), std::move(modifiers), std::move(identifier), |
| std::move(composed_protocols), std::move(methods)); |
| } |
| |
| std::unique_ptr<RawResourceProperty> Parser::ParseResourcePropertyDeclaration() { |
| ASTScope scope(this); |
| auto attributes = MaybeParseAttributeList(); |
| if (!Ok()) |
| return Fail(); |
| |
| auto identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| auto type_ctor = ParseTypeConstructor(); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawResourceProperty>(scope.GetSourceElement(), std::move(type_ctor), |
| std::move(identifier), std::move(attributes)); |
| } |
| |
| std::unique_ptr<RawResourceDeclaration> Parser::ParseResourceDeclaration( |
| std::unique_ptr<RawAttributeList> attributes, ASTScope& scope) { |
| std::vector<std::unique_ptr<RawResourceProperty>> properties; |
| |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kResourceDefinition)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| std::unique_ptr<RawTypeConstructor> maybe_type_ctor; |
| if (MaybeConsumeToken(OfKind(Token::Kind::kColon))) { |
| ASTScope type_identifier_scope(this); |
| auto resource_type_identifier = ParseCompoundIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| maybe_type_ctor = std::make_unique<RawTypeConstructor>( |
| scope.GetSourceElement(), |
| std::make_unique<RawNamedLayoutReference>(type_identifier_scope.GetSourceElement(), |
| std::move(resource_type_identifier)), |
| /*parameters=*/nullptr, |
| /*constraints=*/nullptr); |
| } |
| |
| ConsumeToken(OfKind(Token::Kind::kLeftCurly)); |
| if (!Ok()) |
| return Fail(); |
| |
| // Just the scaffolding of the resource here, only properties is currently accepted. |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kProperties)); |
| if (!Ok()) |
| return Fail(); |
| |
| ConsumeToken(OfKind(Token::Kind::kLeftCurly)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto parse_prop = [&properties, this]() { |
| if (Peek().kind() == Token::Kind::kRightCurly) { |
| ConsumeToken(OfKind(Token::Kind::kRightCurly)); |
| return Done; |
| } |
| add(&properties, [&] { return ParseResourcePropertyDeclaration(); }); |
| return More; |
| }; |
| |
| auto checkpoint = reporter_->Checkpoint(); |
| while (parse_prop() == More) { |
| if (!Ok()) { |
| const auto result = RecoverToEndOfMember(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| if (result == RecoverResult::EndOfScope) { |
| continue; |
| } |
| } |
| ConsumeTokenOrRecover(OfKind(Token::Kind::kSemicolon)); |
| } |
| if (!Ok()) |
| Fail(); |
| |
| if (!checkpoint.NoNewErrors()) |
| return nullptr; |
| |
| if (properties.empty()) |
| return Fail(ErrMustHaveOneProperty); |
| |
| // End of properties block. |
| ConsumeToken(OfKind(Token::Kind::kSemicolon)); |
| if (!Ok()) |
| return Fail(); |
| |
| // End of resource. |
| ConsumeToken(OfKind(Token::Kind::kRightCurly)); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawResourceDeclaration>(scope.GetSourceElement(), std::move(attributes), |
| std::move(identifier), std::move(maybe_type_ctor), |
| std::move(properties)); |
| } |
| |
| std::unique_ptr<RawServiceMember> Parser::ParseServiceMember() { |
| ASTScope scope(this); |
| auto attributes = MaybeParseAttributeList(); |
| if (!Ok()) |
| return Fail(); |
| |
| auto identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| auto type_ctor = ParseTypeConstructor(); |
| if (!Ok()) |
| return Fail(); |
| |
| return std::make_unique<RawServiceMember>(scope.GetSourceElement(), std::move(type_ctor), |
| std::move(identifier), std::move(attributes)); |
| } |
| |
| std::unique_ptr<RawServiceDeclaration> Parser::ParseServiceDeclaration( |
| std::unique_ptr<RawAttributeList> attributes, ASTScope& scope) { |
| std::vector<std::unique_ptr<RawServiceMember>> members; |
| |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kService)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| ConsumeToken(OfKind(Token::Kind::kLeftCurly)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto parse_member = [&]() { |
| if (Peek().kind() == Token::Kind::kRightCurly) { |
| ConsumeToken(OfKind(Token::Kind::kRightCurly)); |
| return Done; |
| } |
| add(&members, [&] { return ParseServiceMember(); }); |
| return More; |
| }; |
| |
| while (parse_member() == More) { |
| if (!Ok()) { |
| const auto result = RecoverToEndOfMember(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| if (result == RecoverResult::EndOfScope) { |
| continue; |
| } |
| } |
| ConsumeTokenOrRecover(OfKind(Token::Kind::kSemicolon)); |
| } |
| if (!Ok()) |
| Fail(); |
| |
| return std::make_unique<RawServiceDeclaration>(scope.GetSourceElement(), std::move(attributes), |
| std::move(identifier), std::move(members)); |
| } |
| |
| std::unique_ptr<RawLayoutParameter> Parser::ParseLayoutParameter() { |
| ASTScope scope(this); |
| |
| switch (Peek().combined()) { |
| TOKEN_LITERAL_CASES: { |
| auto literal = ParseLiteral(); |
| if (!Ok()) |
| return Fail(); |
| auto constant = std::make_unique<RawLiteralConstant>(std::move(literal)); |
| return std::make_unique<RawLiteralLayoutParameter>(scope.GetSourceElement(), |
| std::move(constant)); |
| } |
| default: { |
| auto type_ctor = ParseTypeConstructor(); |
| if (!Ok()) |
| return Fail(); |
| |
| // For non-anonymous type constructors like "foo<T>" or "foo:optional," the presence of type |
| // parameters and constraints, respectively, confirms that "foo" refers to a type reference. |
| // In cases with no type parameters or constraints present (ie, just "foo"), it is impossible |
| // to deduce whether "foo" refers to a type or a value. In such cases, we must discard the |
| // recently built type constructor, and convert it to a compound identifier instead. |
| if (type_ctor->layout_ref->kind == RawLayoutReference::Kind::kNamed && |
| type_ctor->parameters == nullptr && type_ctor->constraints == nullptr) { |
| auto named_ref = static_cast<RawNamedLayoutReference*>(type_ctor->layout_ref.get()); |
| return std::make_unique<RawIdentifierLayoutParameter>(scope.GetSourceElement(), |
| std::move(named_ref->identifier)); |
| } |
| return std::make_unique<RawTypeLayoutParameter>(scope.GetSourceElement(), std::move(type_ctor)); |
| } |
| } |
| } |
| |
| std::unique_ptr<RawLayoutParameterList> Parser::MaybeParseLayoutParameterList() { |
| ASTScope scope(this); |
| if (!MaybeConsumeToken(OfKind(Token::Kind::kLeftAngle))) { |
| return nullptr; |
| } |
| |
| std::vector<std::unique_ptr<RawLayoutParameter>> params; |
| for (;;) { |
| params.emplace_back(ParseLayoutParameter()); |
| if (!Ok()) |
| return Fail(); |
| if (!MaybeConsumeToken(OfKind(Token::Kind::kComma))) |
| break; |
| } |
| |
| if (!ConsumeToken(OfKind(Token::Kind::kRightAngle))) |
| return Fail(); |
| |
| return std::make_unique<RawLayoutParameterList>(scope.GetSourceElement(), std::move(params)); |
| } |
| |
| std::unique_ptr<RawTypeConstraints> Parser::ParseTypeConstraints() { |
| ASTScope scope(this); |
| bool bracketed = false; |
| std::vector<std::unique_ptr<RawConstant>> constraints; |
| if (MaybeConsumeToken(OfKind(Token::Kind::kLeftAngle))) { |
| bracketed = true; |
| } |
| |
| for (;;) { |
| constraints.emplace_back(ParseConstant()); |
| if (!Ok()) |
| return Fail(); |
| if (!bracketed) |
| break; |
| if (!MaybeConsumeToken(OfKind(Token::Kind::kComma))) |
| break; |
| } |
| |
| if (bracketed) { |
| ConsumeTokenOrRecover(OfKind(Token::Kind::kRightAngle)); |
| } else { |
| ZX_ASSERT_MSG(constraints.size() == 1, "only parse one constraint when no brackets present"); |
| } |
| return std::make_unique<RawTypeConstraints>(scope.GetSourceElement(), std::move(constraints)); |
| } |
| |
| std::unique_ptr<RawLayoutMember> Parser::ParseLayoutMember(RawLayoutMember::Kind kind) { |
| ASTScope scope(this); |
| |
| auto attributes = MaybeParseAttributeList(); |
| if (!Ok()) |
| return Fail(); |
| |
| std::unique_ptr<RawOrdinal64> ordinal = nullptr; |
| std::unique_ptr<RawIdentifier> identifier = nullptr; |
| if (kind == RawLayoutMember::Kind::kOrdinaled) { |
| ordinal = ParseOrdinal64(); |
| if (!Ok()) |
| return Fail(); |
| |
| bool identifier_is_reserved = Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kReserved); |
| identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| if (identifier_is_reserved && Peek().kind() == Token::Kind::kSemicolon) { |
| return Fail(ErrReservedNotAllowed); |
| } |
| } |
| |
| if (identifier == nullptr) { |
| identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| } |
| |
| std::unique_ptr<RawTypeConstructor> layout = nullptr; |
| if (kind != RawLayoutMember::Kind::kValue) { |
| layout = ParseTypeConstructor(); |
| if (!Ok()) |
| return Fail(); |
| } |
| |
| // An equal sign followed by a constant (aka, a default value) is optional for |
| // a struct member, but required for a value member. |
| std::unique_ptr<RawConstant> value = nullptr; |
| if (kind == RawLayoutMember::Kind::kStruct && MaybeConsumeToken(OfKind(Token::Kind::kEqual))) { |
| if (!Ok()) |
| return Fail(); |
| value = ParseConstant(); |
| if (!Ok()) |
| return Fail(); |
| } else if (kind == RawLayoutMember::Kind::kValue) { |
| ConsumeToken(OfKind(Token::Kind::kEqual)); |
| if (!Ok()) |
| return Fail(); |
| |
| value = ParseConstant(); |
| if (!Ok()) |
| return Fail(); |
| } |
| |
| switch (kind) { |
| case RawLayoutMember::Kind::kOrdinaled: { |
| return std::make_unique<RawOrdinaledLayoutMember>(scope.GetSourceElement(), |
| std::move(attributes), std::move(ordinal), |
| std::move(identifier), std::move(layout)); |
| } |
| case RawLayoutMember::Kind::kStruct: { |
| return std::make_unique<RawStructLayoutMember>(scope.GetSourceElement(), |
| std::move(attributes), std::move(identifier), |
| std::move(layout), std::move(value)); |
| } |
| case RawLayoutMember::Kind::kValue: { |
| return std::make_unique<RawValueLayoutMember>(scope.GetSourceElement(), std::move(attributes), |
| std::move(identifier), std::move(value)); |
| } |
| } |
| } |
| |
| std::unique_ptr<RawLayout> Parser::ParseLayout( |
| ASTScope& scope, std::unique_ptr<RawModifiers> modifiers, |
| std::unique_ptr<RawCompoundIdentifier> compound_identifier, |
| std::unique_ptr<RawTypeConstructor> subtype_ctor) { |
| RawLayout::Kind layout_kind; |
| RawLayoutMember::Kind member_kind; |
| |
| if (compound_identifier->components.size() != 1) { |
| return Fail(ErrInvalidLayoutClass); |
| } |
| std::unique_ptr<RawIdentifier> identifier = std::move(compound_identifier->components[0]); |
| |
| if (identifier->span().data() == "bits") { |
| if (modifiers != nullptr) |
| ValidateModifiers<Strictness>(modifiers, identifier->start_token); |
| layout_kind = RawLayout::Kind::kBits; |
| member_kind = RawLayoutMember::Kind::kValue; |
| } else if (identifier->span().data() == "enum") { |
| if (modifiers != nullptr) |
| ValidateModifiers<Strictness>(modifiers, identifier->start_token); |
| layout_kind = RawLayout::Kind::kEnum; |
| member_kind = RawLayoutMember::Kind::kValue; |
| } else if (identifier->span().data() == "struct") { |
| if (modifiers != nullptr) |
| ValidateModifiers<Resourceness>(modifiers, identifier->start_token); |
| layout_kind = RawLayout::Kind::kStruct; |
| member_kind = RawLayoutMember::Kind::kStruct; |
| } else if (identifier->span().data() == "table") { |
| if (modifiers != nullptr) |
| ValidateModifiers<Resourceness>(modifiers, identifier->start_token); |
| layout_kind = RawLayout::Kind::kTable; |
| member_kind = RawLayoutMember::Kind::kOrdinaled; |
| } else if (identifier->span().data() == "union") { |
| if (modifiers != nullptr) |
| ValidateModifiers<Strictness, Resourceness>(modifiers, identifier->start_token); |
| layout_kind = RawLayout::Kind::kUnion; |
| member_kind = RawLayoutMember::Kind::kOrdinaled; |
| } else if (experimental_flags_.IsEnabled(ExperimentalFlag::kZxCTypes) && |
| identifier->span().data() == "overlay") { |
| if (modifiers != nullptr) |
| ValidateModifiers<Strictness>(modifiers, identifier->start_token); |
| layout_kind = RawLayout::Kind::kOverlay; |
| member_kind = RawLayoutMember::Kind::kOrdinaled; |
| } else { |
| return Fail(ErrInvalidLayoutClass); |
| } |
| |
| if (member_kind != RawLayoutMember::Kind::kValue && subtype_ctor != nullptr) { |
| return Fail(ErrCannotSpecifySubtype, identifier->start_token.kind_and_subkind()); |
| } |
| |
| ConsumeToken(OfKind(Token::Kind::kLeftCurly)); |
| if (!Ok()) |
| return Fail(); |
| |
| std::vector<std::unique_ptr<RawLayoutMember>> members; |
| auto parse_member = [&]() { |
| if (Peek().kind() == Token::Kind::kRightCurly) { |
| ConsumeToken(OfKind(Token::Kind::kRightCurly)); |
| return Done; |
| } |
| add(&members, [&] { return ParseLayoutMember(member_kind); }); |
| return More; |
| }; |
| |
| auto checkpoint = reporter_->Checkpoint(); |
| while (parse_member() == More) { |
| if (!Ok()) { |
| const auto result = RecoverToEndOfMember(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| if (result == RecoverResult::EndOfScope) { |
| continue; |
| } |
| } |
| ConsumeTokenOrRecover(OfKind(Token::Kind::kSemicolon)); |
| } |
| if (!Ok()) |
| return Fail(); |
| |
| // avoid returning a empty type related errors if there was an error while |
| // parsing the members |
| if (!checkpoint.NoNewErrors()) |
| return nullptr; |
| |
| return std::make_unique<RawLayout>(scope.GetSourceElement(), layout_kind, std::move(members), |
| std::move(modifiers), std::move(subtype_ctor)); |
| } |
| |
| // The colon character is ambiguous. Consider the following two examples: |
| // |
| // type A = enum : foo { BAR = 1; }; |
| // type B = enum : foo; |
| // |
| // When the parser encounters the colon in each case, it has no idea whether the |
| // value immediately after it should be interpreted as the wrapped type in an |
| // inline layout of kind enum, or otherwise as the only constraint on a named |
| // layout called "enum." |
| // |
| // To resolve this confusion, we parse the token after the colon as a constant, |
| // then check to see if the token after that is a left curly brace. If it is, we |
| // assume that this is in fact the inline layout case ("type A"). If it is not, |
| // we assume that it is a named layout with constraints ("type B"). If a parse |
| // failure occurs, std::monostate is returned. |
| ConstraintOrSubtype Parser::ParseTokenAfterColon() { |
| std::unique_ptr<RawTypeConstructor> type_ctor; |
| std::unique_ptr<RawTypeConstraints> constraints; |
| ConsumeToken(OfKind(Token::Kind::kColon)); |
| if (!Ok()) { |
| Fail(); |
| return std::monostate(); |
| } |
| ASTScope scope(this); |
| |
| // If the token after the colon is the opener to a constraints list, we know |
| // for sure that the identifier before the colon must be a |
| // NamedLayoutReference, so none of the other checks in this case are |
| // required. |
| if (Peek().kind() == Token::Kind::kLeftAngle) { |
| return constraints; |
| } |
| |
| std::unique_ptr<RawConstant> constraint_or_subtype = ParseConstant(); |
| if (!Ok()) { |
| Fail(); |
| return std::monostate(); |
| } |
| |
| // If the token after the constant is not an open brace, this was actually a |
| // one-entry constraints block the whole time, so it should be parsed as such. |
| if (Peek().kind() != Token::Kind::kLeftCurly) { |
| std::vector<std::unique_ptr<RawConstant>> components; |
| components.emplace_back(std::move(constraint_or_subtype)); |
| return std::make_unique<RawTypeConstraints>(scope.GetSourceElement(), std::move(components)); |
| } |
| |
| // The token we just parsed as a constant is in fact a layout subtype. Coerce |
| // it into that class. |
| if (constraint_or_subtype->kind != RawConstant::Kind::kIdentifier) { |
| Fail(ErrInvalidWrappedType); |
| return std::monostate(); |
| } |
| |
| auto subtype_element = |
| SourceElement(constraint_or_subtype->start_token, constraint_or_subtype->end_token); |
| auto subtype_constant = static_cast<RawIdentifierConstant*>(constraint_or_subtype.get()); |
| auto subtype_ref = std::make_unique<RawNamedLayoutReference>( |
| subtype_element, std::move(subtype_constant->identifier)); |
| return std::make_unique<RawTypeConstructor>(subtype_element, std::move(subtype_ref), |
| /*parameters=*/nullptr, /*constraints=*/nullptr); |
| } |
| |
| using NamedOrInline = |
| std::variant<std::unique_ptr<RawCompoundIdentifier>, std::unique_ptr<RawLayout>>; |
| |
| // [ name | { ... } ][ < ... > ][ : ... ] |
| std::unique_ptr<RawTypeConstructor> Parser::ParseTypeConstructor() { |
| ASTScope scope(this); |
| std::unique_ptr<RawLayoutReference> layout_ref; |
| std::unique_ptr<RawLayoutParameterList> parameters; |
| std::unique_ptr<RawTypeConstraints> constraints; |
| NamedOrInline layout; |
| auto attributes = MaybeParseAttributeList(); |
| |
| // Everything except for the (optional) attributes at the start of the type constructor |
| // declaration is placed in its own scope. This is done because in cases of type-level attributes |
| // like this |
| // |
| // «@foo @bar «struct MyStruct { ... }»»; |
| // |
| // the start and end of the type_ctor and layout SourceElements should begin before and after the |
| // attributes block, respectively. |
| { |
| ASTScope layout_scope(this); |
| bool resourceness_comes_first = false; |
| std::unique_ptr<RawModifiers> modifiers; |
| std::unique_ptr<RawCompoundIdentifier> identifier; |
| std::optional<RawModifier<Strictness>> maybe_strictness = std::nullopt; |
| std::optional<RawModifier<Resourceness>> maybe_resourceness = std::nullopt; |
| |
| // Consume tokens until we get one that isn't a modifier, treating duplicates |
| // and conflicts as immediately recovered errors. For conflicts (e.g. "strict |
| // flexible" or "flexible strict"), we use the earliest one. |
| for (;;) { |
| if (Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kStrict) || |
| Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kFlexible) || |
| Peek().combined() == CASE_IDENTIFIER(Token::Subkind::kResource)) { |
| ASTScope maybe_compound_identifier_scope(this); |
| auto modifier_subkind = Peek().subkind(); |
| auto maybe_modifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| // Special case: this is either a reference to a type named "flexible/strict/resource" (ex: |
| // `struct { foo resource; };`), or otherwise the first modifier on an inline type |
| // definition (ex: `struct { foo resource union {...}; };`). The only way to decide which |
| // is which is to peek ahead: if the next token is not an identifier, we assume that the |
| // last parsed modifier is actually the identifier of a named value instead. For example, |
| // if the next token after this one isn't an identifier, we're looking at something like: |
| // |
| // strict resource; |
| // |
| // If that's the case, the user is referencing a type named "flexible/strict/resource." This |
| // will need special handling to properly reclassify this modifier as the identifier for the |
| // whole TypeConstructorNew being built here. |
| if (Peek().kind() != Token::kIdentifier) { |
| // Looks like we're dealing with named layout reference that has unfortunately been named |
| // "flexible/strict/resource." |
| identifier = |
| ParseCompoundIdentifier(maybe_compound_identifier_scope, std::move(maybe_modifier)); |
| break; |
| } |
| |
| const Token& modifier_token = maybe_modifier->start_token; |
| switch (modifier_subkind) { |
| case Token::Subkind::kFlexible: |
| case Token::Subkind::kStrict: { |
| auto as_strictness = modifier_subkind == Token::Subkind::kFlexible |
| ? Strictness::kFlexible |
| : Strictness::kStrict; |
| if (maybe_strictness.has_value() && maybe_strictness->value == as_strictness) { |
| Fail(ErrDuplicateModifier, modifier_token, modifier_token.kind_and_subkind()); |
| RecoverOneError(); |
| break; |
| } |
| if (maybe_strictness.has_value()) { |
| Fail(ErrConflictingModifier, modifier_token, modifier_token.kind_and_subkind(), |
| Token::KindAndSubkind(modifier_subkind == Token::Subkind::kFlexible |
| ? Token::Subkind::kStrict |
| : Token::Subkind::kFlexible)); |
| RecoverOneError(); |
| break; |
| } |
| maybe_strictness.emplace(as_strictness, maybe_modifier->start_token); |
| break; |
| } |
| case Token::Subkind::kResource: { |
| if (maybe_resourceness.has_value() && |
| maybe_resourceness->value == Resourceness::kResource) { |
| Fail(ErrDuplicateModifier, modifier_token, modifier_token.kind_and_subkind()); |
| RecoverOneError(); |
| break; |
| } |
| if (maybe_strictness == std::nullopt) { |
| resourceness_comes_first = true; |
| } |
| maybe_resourceness.emplace(Resourceness::kResource, maybe_modifier->start_token); |
| break; |
| } |
| default: { |
| ZX_PANIC("expected modifier token"); |
| } |
| } |
| } else { |
| if (maybe_strictness.has_value() || maybe_resourceness.has_value()) { |
| modifiers = |
| std::make_unique<RawModifiers>(layout_scope.GetSourceElement(), maybe_resourceness, |
| maybe_strictness, resourceness_comes_first); |
| } |
| break; |
| } |
| } |
| |
| // Any type constructor which is not a reference to a type named "flexible/strict/resource" will |
| // have the identifier unset, and will enter the block below to parse it. |
| if (identifier == nullptr) { |
| identifier = ParseCompoundIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| } |
| |
| switch (Peek().kind()) { |
| case Token::Kind::kLeftCurly: { |
| layout = ParseLayout(layout_scope, std::move(modifiers), std::move(identifier), |
| /*subtype_ctor=*/nullptr); |
| if (!Ok()) |
| return Fail(); |
| break; |
| } |
| case Token::Kind::kColon: { |
| ConstraintOrSubtype after_colon = ParseTokenAfterColon(); |
| std::visit(matchers{ |
| [&](std::unique_ptr<RawTypeConstraints>& constraint) -> void { |
| if (constraints != nullptr) { |
| Fail(ErrMultipleConstraintDefinitions, previous_token_.span()); |
| } |
| if (modifiers != nullptr) |
| ValidateModifiers</* none */>(modifiers, identifier->start_token); |
| if (attributes != nullptr) |
| Fail(ErrCannotAttachAttributeToIdentifier, attributes->span()); |
| constraints = std::move(constraint); |
| layout = std::move(identifier); |
| }, |
| [&](std::unique_ptr<RawTypeConstructor>& type_ctor) -> void { |
| layout = ParseLayout(layout_scope, std::move(modifiers), |
| std::move(identifier), std::move(type_ctor)); |
| if (!Ok()) { |
| Fail(); |
| } |
| }, |
| [&](std::monostate _) -> void { ZX_ASSERT(!Ok()); }, |
| }, |
| after_colon); |
| |
| if (!Ok()) { |
| return nullptr; |
| } |
| break; |
| } |
| default: { |
| if (modifiers != nullptr) |
| ValidateModifiers</* none */>(modifiers, identifier->start_token); |
| if (attributes != nullptr) |
| Fail(ErrCannotAttachAttributeToIdentifier, attributes->span()); |
| layout = std::move(identifier); |
| } |
| } |
| } |
| |
| // Build a LayoutReference of the right type based on the underlying type of the layout. |
| ZX_ASSERT_MSG(std::holds_alternative<std::unique_ptr<RawCompoundIdentifier>>(layout) || |
| std::holds_alternative<std::unique_ptr<RawLayout>>(layout), |
| "must have set layout by this point"); |
| std::visit(matchers{ |
| [&](std::unique_ptr<RawCompoundIdentifier>& named_layout) -> void { |
| layout_ref = std::make_unique<RawNamedLayoutReference>( |
| SourceElement(named_layout->start_token, named_layout->end_token), |
| std::move(named_layout)); |
| }, |
| [&](std::unique_ptr<RawLayout>& inline_layout) -> void { |
| layout_ref = std::make_unique<RawInlineLayoutReference>( |
| scope.GetSourceElement(), std::move(attributes), std::move(inline_layout)); |
| }, |
| }, |
| layout); |
| |
| if (previous_token_.kind() != Token::Kind::kColon) { |
| parameters = MaybeParseLayoutParameterList(); |
| if (!Ok()) |
| return Fail(); |
| } |
| |
| MaybeConsumeToken(OfKind(Token::Kind::kColon)); |
| if (previous_token_.kind() == Token::Kind::kColon) { |
| if (constraints != nullptr) { |
| return Fail(ErrMultipleConstraintDefinitions, previous_token_.span()); |
| } |
| constraints = ParseTypeConstraints(); |
| if (!Ok()) |
| return Fail(); |
| } |
| |
| ZX_ASSERT_MSG(layout_ref != nullptr, |
| "ParseTypeConstructor must always produce a non-null layout_ref"); |
| return std::make_unique<RawTypeConstructor>(scope.GetSourceElement(), std::move(layout_ref), |
| std::move(parameters), std::move(constraints)); |
| } |
| |
| std::unique_ptr<RawTypeDeclaration> Parser::ParseTypeDeclaration( |
| std::unique_ptr<RawAttributeList> attributes, ASTScope& scope) { |
| ConsumeToken(IdentifierOfSubkind(Token::Subkind::kType)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto identifier = ParseIdentifier(); |
| if (!Ok()) |
| return Fail(); |
| |
| ConsumeToken(OfKind(Token::Kind::kEqual)); |
| if (!Ok()) |
| return Fail(); |
| |
| auto layout = ParseTypeConstructor(); |
| if (!Ok()) |
| return Fail(); |
| |
| bool layout_has_attributes = |
| layout->layout_ref->kind == RawLayoutReference::Kind::kInline && |
| static_cast<RawInlineLayoutReference*>(layout->layout_ref.get())->attributes != nullptr; |
| if (attributes != nullptr && layout_has_attributes) |
| return Fail(ErrRedundantAttributePlacement, scope.GetSourceElement().span()); |
| return std::make_unique<RawTypeDeclaration>(scope.GetSourceElement(), std::move(attributes), |
| std::move(identifier), std::move(layout)); |
| } |
| |
| std::unique_ptr<File> Parser::ParseFile() { |
| ASTScope scope(this); |
| |
| ConsumeToken(OfKind(Token::Kind::kStartOfFile)); |
| auto library_decl = ParseLibraryDeclaration(); |
| if (!Ok()) |
| return Fail(); |
| ConsumeToken(OfKind(Token::Kind::kSemicolon)); |
| if (!Ok()) |
| return Fail(); |
| |
| bool done_with_library_imports = false; |
| std::vector<std::unique_ptr<RawAliasDeclaration>> alias_list; |
| std::vector<std::unique_ptr<RawUsing>> using_list; |
| std::vector<std::unique_ptr<RawConstDeclaration>> const_declaration_list; |
| std::vector<std::unique_ptr<RawProtocolDeclaration>> protocol_declaration_list; |
| std::vector<std::unique_ptr<RawResourceDeclaration>> resource_declaration_list; |
| std::vector<std::unique_ptr<RawServiceDeclaration>> service_declaration_list; |
| std::vector<std::unique_ptr<RawTypeDeclaration>> type_decls; |
| auto parse_declaration = [&]() { |
| ASTScope scope(this); |
| std::unique_ptr<RawAttributeList> attributes = MaybeParseAttributeList(); |
| if (!Ok()) |
| return More; |
| |
| switch (Peek().combined()) { |
| default: |
| Fail(ErrExpectedDeclaration, last_token_.data()); |
| return More; |
| |
| case CASE_TOKEN(Token::Kind::kEndOfFile): |
| return Done; |
| |
| case CASE_IDENTIFIER(Token::Subkind::kAlias): { |
| done_with_library_imports = true; |
| add(&alias_list, [&] { return ParseAliasDeclaration(std::move(attributes), scope); }); |
| return More; |
| } |
| |
| case CASE_IDENTIFIER(Token::Subkind::kConst): { |
| done_with_library_imports = true; |
| add(&const_declaration_list, |
| [&] { return ParseConstDeclaration(std::move(attributes), scope); }); |
| return More; |
| } |
| |
| case CASE_IDENTIFIER(Token::Subkind::kType): { |
| done_with_library_imports = true; |
| add(&type_decls, [&] { return ParseTypeDeclaration(std::move(attributes), scope); }); |
| return More; |
| } |
| |
| case CASE_IDENTIFIER(Token::Subkind::kAjar): |
| case CASE_IDENTIFIER(Token::Subkind::kClosed): |
| case CASE_IDENTIFIER(Token::Subkind::kOpen): |
| case CASE_IDENTIFIER(Token::Subkind::kProtocol): { |
| done_with_library_imports = true; |
| add(&protocol_declaration_list, |
| [&] { return ParseProtocolDeclaration(std::move(attributes), scope); }); |
| return More; |
| } |
| |
| case CASE_IDENTIFIER(Token::Subkind::kResourceDefinition): { |
| done_with_library_imports = true; |
| add(&resource_declaration_list, |
| [&] { return ParseResourceDeclaration(std::move(attributes), scope); }); |
| return More; |
| } |
| |
| case CASE_IDENTIFIER(Token::Subkind::kService): { |
| done_with_library_imports = true; |
| add(&service_declaration_list, |
| [&] { return ParseServiceDeclaration(std::move(attributes), scope); }); |
| return More; |
| } |
| |
| case CASE_IDENTIFIER(Token::Subkind::kUsing): { |
| add(&using_list, [&] { return ParseUsing(std::move(attributes), scope); }); |
| if (Ok() && done_with_library_imports) { |
| Fail(ErrLibraryImportsMustBeGroupedAtTopOfFile, using_list.back()->span()); |
| } |
| return More; |
| } |
| } |
| }; |
| |
| while (parse_declaration() == More) { |
| if (!Ok()) { |
| // If this returns RecoverResult::Continue, we have consumed up to a '}' |
| // and expect a ';' to follow. |
| auto result = RecoverToEndOfDecl(); |
| if (result == RecoverResult::Failure) { |
| return Fail(); |
| } |
| if (result == RecoverResult::EndOfScope) { |
| break; |
| } |
| } |
| ConsumeTokenOrRecover(OfKind(Token::Kind::kSemicolon)); |
| } |
| |
| std::optional<Token> end = ConsumeToken(OfKind(Token::Kind::kEndOfFile)); |
| if (!Ok() || !end) |
| return Fail(); |
| |
| return std::make_unique<File>( |
| scope.GetSourceElement(), std::move(library_decl), std::move(alias_list), |
| std::move(using_list), std::move(const_declaration_list), |
| std::move(protocol_declaration_list), std::move(resource_declaration_list), |
| std::move(service_declaration_list), std::move(type_decls), std::move(tokens_)); |
| } |
| |
| bool Parser::ConsumeTokensUntil(std::set<Token::Kind> exit_tokens) { |
| auto p = [&](const Token& token) -> std::unique_ptr<Diagnostic> { |
| if (exit_tokens.count(token.kind()) > 0) { |
| // signal to ReadToken to stop by returning an error |
| return Diagnostic::MakeError(ErrUnexpectedToken, token.span()); |
| } |
| // nullptr return value indicates -> yes, consume to ReadToken |
| return nullptr; |
| }; |
| |
| // Consume tokens until we find a synchronization point |
| while (ReadToken(p, OnNoMatch::kIgnore) != std::nullopt) { |
| if (!Ok()) |
| return false; |
| } |
| return true; |
| } |
| |
| Parser::RecoverResult Parser::RecoverToEndOfAttributeNew() { |
| if (ConsumedEOF()) { |
| return RecoverResult::Failure; |
| } |
| |
| RecoverAllErrors(); |
| |
| static const auto exit_tokens = std::set<Token::Kind>{ |
| Token::Kind::kRightParen, |
| Token::Kind::kEndOfFile, |
| }; |
| if (!ConsumeTokensUntil(exit_tokens)) { |
| return RecoverResult::Failure; |
| } |
| |
| switch (Peek().combined()) { |
| case CASE_TOKEN(Token::Kind::kRightParen): |
| ConsumeToken(OfKind(Token::Kind::kRightParen)); |
| if (!Ok()) |
| return RecoverResult::Failure; |
| return RecoverResult::Continue; |
| case CASE_TOKEN(Token::Kind::kEndOfFile): |
| return RecoverResult::EndOfScope; |
| default: |
| return RecoverResult::Failure; |
| } |
| } |
| |
| Parser::RecoverResult Parser::RecoverToEndOfDecl() { |
| if (ConsumedEOF()) { |
| return RecoverResult::Failure; |
| } |
| |
| RecoverAllErrors(); |
| |
| static const auto exit_tokens = std::set<Token::Kind>{ |
| Token::Kind::kRightCurly, |
| Token::Kind::kEndOfFile, |
| }; |
| if (!ConsumeTokensUntil(exit_tokens)) { |
| return RecoverResult::Failure; |
| } |
| |
| switch (Peek().combined()) { |
| case CASE_TOKEN(Token::Kind::kRightCurly): |
| ConsumeToken(OfKind(Token::Kind::kRightCurly)); |
| if (!Ok()) |
| return RecoverResult::Failure; |
| return RecoverResult::Continue; |
| case CASE_TOKEN(Token::Kind::kEndOfFile): |
| return RecoverResult::EndOfScope; |
| default: |
| return RecoverResult::Failure; |
| } |
| } |
| |
| Parser::RecoverResult Parser::RecoverToEndOfMember() { |
| if (ConsumedEOF()) { |
| return RecoverResult::Failure; |
| } |
| |
| RecoverAllErrors(); |
| |
| static const auto exit_tokens = std::set<Token::Kind>{ |
| Token::Kind::kSemicolon, |
| Token::Kind::kRightCurly, |
| Token::Kind::kEndOfFile, |
| }; |
| if (!ConsumeTokensUntil(exit_tokens)) { |
| return RecoverResult::Failure; |
| } |
| |
| switch (Peek().combined()) { |
| case CASE_TOKEN(Token::Kind::kSemicolon): |
| return RecoverResult::Continue; |
| case CASE_TOKEN(Token::Kind::kRightCurly): |
| return RecoverResult::EndOfScope; |
| default: |
| return RecoverResult::Failure; |
| } |
| } |
| |
| template <Token::Kind ClosingToken> |
| Parser::RecoverResult Parser::RecoverToEndOfListItem() { |
| if (ConsumedEOF()) { |
| return RecoverResult::Failure; |
| } |
| |
| RecoverAllErrors(); |
| |
| static const auto exit_tokens = std::set<Token::Kind>{ |
| Token::Kind::kComma, |
| Token::Kind::kSemicolon, |
| Token::Kind::kRightCurly, |
| Token::Kind::kEndOfFile, |
| ClosingToken, |
| }; |
| if (!ConsumeTokensUntil(exit_tokens)) { |
| return RecoverResult::Failure; |
| } |
| |
| switch (Peek().combined()) { |
| case CASE_TOKEN(Token::Kind::kComma): |
| return RecoverResult::Continue; |
| case CASE_TOKEN(ClosingToken): |
| return RecoverResult::EndOfScope; |
| default: |
| return RecoverResult::Failure; |
| } |
| } |
| |
| Parser::RecoverResult Parser::RecoverToEndOfAttributeArg() { |
| return RecoverToEndOfListItem<Token::Kind::kRightParen>(); |
| } |
| |
| Parser::RecoverResult Parser::RecoverToEndOfParam() { |
| return RecoverToEndOfListItem<Token::Kind::kRightParen>(); |
| } |
| |
| Parser::RecoverResult Parser::RecoverToEndOfParamList() { |
| if (ConsumedEOF()) { |
| return RecoverResult::Failure; |
| } |
| |
| RecoverAllErrors(); |
| |
| static const auto exit_tokens = std::set<Token::Kind>{ |
| Token::Kind::kRightParen, |
| Token::Kind::kEndOfFile, |
| }; |
| if (!ConsumeTokensUntil(exit_tokens)) { |
| return RecoverResult::Failure; |
| } |
| |
| switch (Peek().combined()) { |
| case CASE_TOKEN(Token::Kind::kRightParen): |
| return RecoverResult::EndOfScope; |
| default: |
| return RecoverResult::Failure; |
| } |
| } |
| |
| } // namespace fidlc |