| // Copyright 2020 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 "src/lib/fidl_codec/semantic_parser.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <cctype> |
| #include <iostream> |
| #include <memory> |
| #include <ostream> |
| #include <string> |
| |
| #include "src/lib/fidl_codec/library_loader.h" |
| |
| namespace fidl_codec { |
| namespace semantic { |
| |
| std::ostream& ParserErrors::AddError() { |
| ++error_count_; |
| return os_; |
| } |
| |
| std::ostream& ParserErrors::AddError(const Location& location) { |
| ++error_count_; |
| // Computes the line and column in the buffer of the error. |
| std::string::const_iterator start_line = location.buffer().begin(); |
| int line = 1; |
| int column = 1; |
| std::string::const_iterator current = start_line; |
| while (current != location.location()) { |
| if (*current == '\n') { |
| start_line = ++current; |
| ++line; |
| column = 1; |
| } else { |
| ++current; |
| ++column; |
| } |
| } |
| while (current != location.buffer().end()) { |
| if (*current == '\n') { |
| break; |
| } |
| ++current; |
| } |
| // Displays the line of the error (the whole line). |
| os_ << std::string_view(&*start_line, current - start_line) << '\n'; |
| // Displays some spaces. This way, the caret will be right under the error location. |
| // It will point the first character of the previous line where the error is. |
| current = start_line; |
| for (int i = 1; i < column; ++i) { |
| os_ << ((*current == '\t') ? '\t' : ' '); |
| ++current; |
| } |
| // Displays the marker (caret) which points to the error, the line and the column. |
| os_ << "^\n" << line << ":" << column << ": "; |
| // Returns the stream. This way, the caller can add the error message. |
| return os_; |
| } |
| |
| void SemanticParser::NextLexicalToken() { |
| bool error_found = false; |
| for (;;) { |
| while (isspace(*next_)) { |
| ++next_; |
| } |
| current_location_ = next_; |
| switch (*next_) { |
| case '\0': |
| current_lexical_token_ = LexicalToken::kEof; |
| return; |
| case '\'': |
| LexerString(); |
| return; |
| case '{': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kLeftBrace; |
| return; |
| case '}': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kRightBrace; |
| return; |
| case '(': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kLeftParenthesis; |
| return; |
| case ')': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kRightParenthesis; |
| return; |
| case ':': |
| ++next_; |
| if (*next_ == ':') { |
| ++next_; |
| current_lexical_token_ = LexicalToken::kColonColon; |
| } else { |
| current_lexical_token_ = LexicalToken::kColon; |
| } |
| return; |
| case ',': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kComma; |
| return; |
| case '.': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kDot; |
| return; |
| case '=': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kEqual; |
| return; |
| case ';': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kSemicolon; |
| return; |
| case '/': |
| ++next_; |
| current_lexical_token_ = LexicalToken::kSlash; |
| return; |
| default: |
| if (isalpha(*next_) || (*next_ == '_')) { |
| LexerIdentifier(); |
| return; |
| } |
| if (!error_found && !ignore_unknown_characters_) { |
| error_found = true; |
| AddError() << "Unknown character <" << *next_ << ">\n"; |
| } |
| ++next_; |
| break; |
| } |
| } |
| } |
| |
| void SemanticParser::JumpToSemicolon() { |
| IgnoreUnknownCharacters ignore_unknown_characters(this); |
| while (!IsEof()) { |
| if (IsSemicolon() || IsRightBrace()) { |
| return; |
| } |
| if (ConsumeLeftParenthesis()) { |
| SkipRightParenthesis(); |
| } else { |
| NextLexicalToken(); |
| } |
| } |
| } |
| |
| void SemanticParser::SkipSemicolon() { |
| IgnoreUnknownCharacters ignore_unknown_characters(this); |
| while (!IsEof()) { |
| if (ConsumeSemicolon() || IsRightBrace()) { |
| return; |
| } |
| if (ConsumeLeftParenthesis()) { |
| SkipRightParenthesis(); |
| } else { |
| NextLexicalToken(); |
| } |
| } |
| } |
| |
| void SemanticParser::SkipBlock() { |
| IgnoreUnknownCharacters ignore_unknown_characters(this); |
| while (!IsEof()) { |
| if (ConsumeRightBrace() || ConsumeSemicolon()) { |
| return; |
| } |
| if (ConsumeLeftBrace()) { |
| SkipRightBrace(); |
| } else { |
| NextLexicalToken(); |
| } |
| } |
| } |
| |
| void SemanticParser::SkipRightBrace() { |
| IgnoreUnknownCharacters ignore_unknown_characters(this); |
| while (!IsEof()) { |
| if (ConsumeRightBrace()) { |
| return; |
| } |
| if (ConsumeLeftBrace()) { |
| SkipRightBrace(); |
| } else { |
| NextLexicalToken(); |
| } |
| } |
| } |
| |
| void SemanticParser::SkipRightParenthesis() { |
| IgnoreUnknownCharacters ignore_unknown_characters(this); |
| while (!IsEof()) { |
| if (ConsumeRightParenthesis() || IsSemicolon()) { |
| return; |
| } |
| if (ConsumeLeftBrace()) { |
| SkipRightBrace(); |
| } else if (ConsumeLeftParenthesis()) { |
| SkipRightParenthesis(); |
| } else { |
| NextLexicalToken(); |
| } |
| } |
| } |
| |
| std::string SemanticParser::ConsumeString() { |
| std::string result = std::string(current_string_); |
| size_t pos = 0; |
| for (;;) { |
| auto backslash = result.find('\\', pos); |
| if (backslash == std::string::npos) { |
| NextLexicalToken(); |
| return result; |
| } |
| // We already checked that backslashes are followed by another character. |
| FX_DCHECK(backslash < result.size() - 1); |
| result.erase(backslash); |
| pos = backslash + 1; |
| } |
| } |
| |
| void SemanticParser::ParseSemantic() { |
| while (!IsEof()) { |
| if (Is("library")) { |
| ParseLibrary(); |
| } else { |
| AddError() << "Keyword 'library' expected.\n"; |
| SkipBlock(); |
| } |
| } |
| } |
| |
| void SemanticParser::ParseLibrary() { |
| allow_dots_in_identifiers_ = true; |
| NextLexicalToken(); |
| if (!IsIdentifier()) { |
| AddError() << "Library name expected.\n"; |
| SkipBlock(); |
| return; |
| } |
| allow_dots_in_identifiers_ = false; |
| Library* library = library_loader_->GetLibraryFromName(std::string(current_string_)); |
| if (library == nullptr) { |
| AddError() << "Library " << current_string_ << " not found.\n"; |
| } else { |
| library->DecodeTypes(); |
| } |
| NextLexicalToken(); |
| if (!ParseLeftBrace()) { |
| SkipBlock(); |
| return; |
| } |
| while (!ConsumeRightBrace()) { |
| if (!IsIdentifier()) { |
| AddError() << "Protocol name expected.\n"; |
| SkipBlock(); |
| NextLexicalToken(); |
| return; |
| } |
| Interface* interface = nullptr; |
| if (library != nullptr) { |
| std::string protocol_name = library->name() + "/" + std::string(current_string_); |
| if (!library->GetInterfaceByName(protocol_name, &interface)) { |
| AddError() << "Protocol " << current_string_ << " not found in library " << library->name() |
| << '\n'; |
| } |
| } |
| NextLexicalToken(); |
| if (!ParseColonColon()) { |
| SkipBlock(); |
| NextLexicalToken(); |
| return; |
| } |
| if (!IsIdentifier()) { |
| AddError() << "Method name expected.\n"; |
| SkipBlock(); |
| NextLexicalToken(); |
| return; |
| } |
| InterfaceMethod* method = nullptr; |
| if (interface != nullptr) { |
| method = interface->GetMethodByName(current_string_); |
| if (method == nullptr) { |
| AddError() << "Method " << current_string_ << " not found in protocol " << interface->name() |
| << '\n'; |
| } |
| } |
| NextLexicalToken(); |
| if (!ParseLeftBrace()) { |
| SkipBlock(); |
| NextLexicalToken(); |
| return; |
| } |
| ParseMethod(method); |
| } |
| } |
| |
| void SemanticParser::ParseMethod(InterfaceMethod* method) { |
| while (!ConsumeRightBrace() && !IsEof()) { |
| if (Is("input_field")) { |
| NextLexicalToken(); |
| ParseColon(); |
| std::unique_ptr<DisplayExpression> expression = ParseDisplayExpression(); |
| if (method != nullptr) { |
| MethodDisplay* display = method->short_display(); |
| if (display == nullptr) { |
| method->set_short_display(std::make_unique<MethodDisplay>()); |
| display = method->short_display(); |
| } |
| display->AddInput(std::move(expression)); |
| } |
| if (!ParseSemicolon()) { |
| SkipSemicolon(); |
| } |
| } else if (Is("result")) { |
| NextLexicalToken(); |
| ParseColon(); |
| std::unique_ptr<DisplayExpression> expression = ParseDisplayExpression(); |
| if (method != nullptr) { |
| MethodDisplay* display = method->short_display(); |
| if (display == nullptr) { |
| method->set_short_display(std::make_unique<MethodDisplay>()); |
| display = method->short_display(); |
| } |
| display->AddResult(std::move(expression)); |
| } |
| if (!ParseSemicolon()) { |
| SkipSemicolon(); |
| } |
| } else { |
| MethodSemantic* method_semantic = nullptr; |
| if (method != nullptr) { |
| method_semantic = method->semantic(); |
| if (method_semantic == nullptr) { |
| method->set_semantic(std::make_unique<MethodSemantic>()); |
| method_semantic = method->semantic(); |
| } |
| } |
| ParseAssignment(method_semantic); |
| } |
| } |
| } |
| |
| std::unique_ptr<DisplayExpression> SemanticParser::ParseDisplayExpression() { |
| auto display_expression = std::make_unique<DisplayExpression>(); |
| if (IsString()) { |
| display_expression->set_header(ConsumeString()); |
| } |
| std::unique_ptr<Expression> expression = ParseExpression(); |
| if (expression != nullptr) { |
| display_expression->set_expression(std::move(expression)); |
| } |
| if (IsString()) { |
| display_expression->set_footer(ConsumeString()); |
| } |
| return display_expression; |
| } |
| |
| void SemanticParser::ParseAssignment(MethodSemantic* method_semantic) { |
| std::unique_ptr<Expression> destination = ParseExpression(); |
| if (destination == nullptr) { |
| AddError() << "Assignment expected.\n"; |
| SkipSemicolon(); |
| return; |
| } |
| if (!ParseEqual()) { |
| SkipSemicolon(); |
| return; |
| } |
| std::unique_ptr<Expression> source = ParseExpression(); |
| if (source == nullptr) { |
| AddError() << "Expression expected.\n"; |
| SkipSemicolon(); |
| return; |
| } |
| if (method_semantic != nullptr) { |
| method_semantic->AddAssignment(std::move(destination), std::move(source)); |
| } |
| if (!ParseSemicolon()) { |
| SkipSemicolon(); |
| } |
| } |
| |
| std::unique_ptr<Expression> SemanticParser::ParseExpression() { |
| return ParseMultiplicativeExpression(); |
| } |
| |
| std::unique_ptr<Expression> SemanticParser::ParseMultiplicativeExpression() { |
| std::unique_ptr<Expression> expression = ParseAccessExpression(); |
| if (expression == nullptr) { |
| return nullptr; |
| } |
| for (;;) { |
| if (ConsumeSlash()) { |
| std::unique_ptr<Expression> right = ParseAccessExpression(); |
| if (right == nullptr) { |
| return nullptr; |
| } |
| expression = std::make_unique<ExpressionSlash>(std::move(expression), std::move(right)); |
| } else if (ConsumeColon()) { |
| std::unique_ptr<Expression> right = ParseAccessExpression(); |
| if (right == nullptr) { |
| return nullptr; |
| } |
| expression = std::make_unique<ExpressionColon>(std::move(expression), std::move(right)); |
| } else { |
| return expression; |
| } |
| } |
| } |
| |
| std::unique_ptr<Expression> SemanticParser::ParseAccessExpression() { |
| std::unique_ptr<Expression> expression = ParseTerminalExpression(); |
| if (expression == nullptr) { |
| return nullptr; |
| } |
| for (;;) { |
| if (ConsumeDot()) { |
| if (!IsIdentifier()) { |
| AddError() << "Field name expected.\n"; |
| expression = std::make_unique<ExpressionFieldAccess>(std::move(expression), ""); |
| } else { |
| std::string_view name = current_string_; |
| NextLexicalToken(); |
| expression = std::make_unique<ExpressionFieldAccess>(std::move(expression), name); |
| } |
| } else { |
| return expression; |
| } |
| } |
| } |
| |
| std::unique_ptr<Expression> SemanticParser::ParseTerminalExpression() { |
| if (IsString()) { |
| return std::make_unique<ExpressionStringLiteral>(ConsumeString()); |
| } |
| if (Consume("request")) { |
| return std::make_unique<ExpressionRequest>(); |
| } |
| if (Consume("handle")) { |
| return std::make_unique<ExpressionHandle>(); |
| } |
| if (Consume("HandleDescription")) { |
| return ParseHandleDescription(); |
| } |
| return nullptr; |
| } |
| |
| std::unique_ptr<Expression> SemanticParser::ParseHandleDescription() { |
| if (!ParseLeftParenthesis()) { |
| JumpToSemicolon(); |
| return std::make_unique<ExpressionHandleDescription>(nullptr, nullptr); |
| } |
| std::unique_ptr<Expression> type = ParseExpression(); |
| if (type == nullptr) { |
| AddError() << "Expression expected (handle type)"; |
| SkipRightParenthesis(); |
| return std::make_unique<ExpressionHandleDescription>(nullptr, nullptr); |
| } |
| if (!ParseComma()) { |
| SkipRightParenthesis(); |
| return std::make_unique<ExpressionHandleDescription>(std::move(type), nullptr); |
| } |
| std::unique_ptr<Expression> path = ParseExpression(); |
| if (path == nullptr) { |
| AddError() << "Expression expected (handle path)"; |
| SkipRightParenthesis(); |
| return std::make_unique<ExpressionHandleDescription>(std::move(type), nullptr); |
| } |
| if (!ParseRightParenthesis()) { |
| SkipRightParenthesis(); |
| } |
| return std::make_unique<ExpressionHandleDescription>(std::move(type), std::move(path)); |
| } |
| |
| void SemanticParser::LexerIdentifier() { |
| std::string::const_iterator start = next_; |
| while (isalnum(*next_) || (*next_ == '_') || ((*next_ == '.') && allow_dots_in_identifiers_)) { |
| ++next_; |
| } |
| current_string_ = std::string_view(&*start, next_ - start); |
| current_lexical_token_ = LexicalToken::kIdentifier; |
| } |
| |
| void SemanticParser::LexerString() { |
| std::string::const_iterator start = ++next_; |
| while (*next_ != '\'') { |
| if (*next_ == '\0') { |
| AddError() << "Unterminated string.\n"; |
| current_lexical_token_ = LexicalToken::kString; |
| next_ = start; |
| current_string_ = std::string_view(&*start, 0); |
| return; |
| } |
| if (*next_ == '\\') { |
| ++next_; |
| if (*next_ == '\0') { |
| AddError() << "Unterminated string.\n"; |
| current_lexical_token_ = LexicalToken::kString; |
| next_ = start; |
| current_string_ = std::string_view(&*start, 0); |
| return; |
| } |
| } |
| ++next_; |
| } |
| current_string_ = std::string_view(&*start, next_ - start); |
| ++next_; |
| current_lexical_token_ = LexicalToken::kString; |
| } |
| |
| } // namespace semantic |
| } // namespace fidl_codec |