| //===-- DILLexer.cpp ------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| // This implements the recursive descent parser for the Data Inspection | |
| // Language (DIL), and its helper functions, which will eventually underlie the | |
| // 'frame variable' command. The language that this parser recognizes is | |
| // described in lldb/docs/dil-expr-lang.ebnf | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lldb/ValueObject/DILLexer.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/ValueObject/DILParser.h" | |
| #include "llvm/ADT/StringSwitch.h" | |
| namespace lldb_private::dil { | |
| llvm::StringRef Token::GetTokenName(Kind kind) { | |
| switch (kind) { | |
| case Kind::amp: | |
| return "amp"; | |
| case Kind::ampamp: | |
| return "ampamp"; | |
| case Kind::ampequal: | |
| return "ampequal"; | |
| case Kind::arrow: | |
| return "arrow"; | |
| case Kind::caret: | |
| return "caret"; | |
| case Kind::caretequal: | |
| return "caretequal"; | |
| case Kind::colon: | |
| return "colon"; | |
| case Kind::coloncolon: | |
| return "coloncolon"; | |
| case Kind::equal: | |
| return "equal"; | |
| case Kind::exclaim: | |
| return "exclaim"; | |
| case Kind::eof: | |
| return "eof"; | |
| case Kind::equalequal: | |
| return "equalequal"; | |
| case Kind::exclaimequal: | |
| return "exclaimequal"; | |
| case Kind::float_constant: | |
| return "float_constant"; | |
| case Kind::greater: | |
| return "greater"; | |
| case Kind::greaterequal: | |
| return "greaterequal"; | |
| case Kind::greatergreater: | |
| return "greatergreater"; | |
| case Kind::greatergreaterequal: | |
| return "greatergreaterequal"; | |
| case Kind::identifier: | |
| return "identifier"; | |
| case Kind::integer_constant: | |
| return "integer_constant"; | |
| case Kind::kw_false: | |
| return "false"; | |
| case Kind::kw_true: | |
| return "true"; | |
| case Kind::l_paren: | |
| return "l_paren"; | |
| case Kind::l_square: | |
| return "l_square"; | |
| case Kind::less: | |
| return "less"; | |
| case Kind::lessequal: | |
| return "lessequal"; | |
| case Kind::lessless: | |
| return "lessless"; | |
| case Kind::lesslessequal: | |
| return "lesslessequal"; | |
| case Kind::minus: | |
| return "minus"; | |
| case Kind::minusequal: | |
| return "minusequal"; | |
| case Kind::minusminus: | |
| return "minusminus"; | |
| case Token::percent: | |
| return "percent"; | |
| case Token::percentequal: | |
| return "percentequal"; | |
| case Kind::period: | |
| return "period"; | |
| case Kind::pipe: | |
| return "pipe"; | |
| case Kind::pipeequal: | |
| return "pipeequal"; | |
| case Kind::pipepipe: | |
| return "pipepipe"; | |
| case Kind::plus: | |
| return "plus"; | |
| case Kind::plusequal: | |
| return "plusequal"; | |
| case Kind::plusplus: | |
| return "plusplus"; | |
| case Kind::question: | |
| return "question"; | |
| case Kind::r_paren: | |
| return "r_paren"; | |
| case Kind::r_square: | |
| return "r_square"; | |
| case Token::slash: | |
| return "slash"; | |
| case Token::slashequal: | |
| return "slashequal"; | |
| case Token::star: | |
| return "star"; | |
| case Token::starequal: | |
| return "starequal"; | |
| case Token::tilde: | |
| return "tilde"; | |
| } | |
| llvm_unreachable("Unknown token name"); | |
| } | |
| static bool IsLetter(char c) { | |
| return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); | |
| } | |
| static bool IsDigit(char c) { return '0' <= c && c <= '9'; } | |
| // A word starts with a letter, underscore, or dollar sign, followed by | |
| // letters ('a'..'z','A'..'Z'), digits ('0'..'9'), and/or underscores. | |
| static std::optional<llvm::StringRef> IsWord(llvm::StringRef expr, | |
| llvm::StringRef &remainder) { | |
| // Find the longest prefix consisting of letters, digits, underscors and | |
| // '$'. If it doesn't start with a digit, then it's a word. | |
| llvm::StringRef candidate = remainder.take_while( | |
| [](char c) { return IsDigit(c) || IsLetter(c) || c == '_' || c == '$'; }); | |
| if (candidate.empty() || IsDigit(candidate[0])) | |
| return std::nullopt; | |
| remainder = remainder.drop_front(candidate.size()); | |
| return candidate; | |
| } | |
| static bool IsNumberBodyChar(char ch) { | |
| return IsDigit(ch) || IsLetter(ch) || ch == '.'; | |
| } | |
| static std::optional<llvm::StringRef> IsNumber(llvm::StringRef &remainder, | |
| bool &isFloat) { | |
| llvm::StringRef tail = remainder; | |
| llvm::StringRef body = tail.take_while(IsNumberBodyChar); | |
| size_t dots = body.count('.'); | |
| if (dots > 1 || dots == body.size()) | |
| return std::nullopt; | |
| if (IsDigit(body.front()) || (body[0] == '.' && IsDigit(body[1]))) { | |
| isFloat = dots == 1; | |
| tail = tail.drop_front(body.size()); | |
| bool isHex = body.contains_insensitive('x'); | |
| bool hasExp = !isHex && body.contains_insensitive('e'); | |
| bool hasHexExp = isHex && body.contains_insensitive('p'); | |
| if (hasExp || hasHexExp) { | |
| isFloat = true; // This marks numbers like 0x1p1 and 1e1 as float | |
| if (body.ends_with_insensitive("e") || body.ends_with_insensitive("p")) | |
| if (tail.consume_front("+") || tail.consume_front("-")) | |
| tail = tail.drop_while(IsNumberBodyChar); | |
| } | |
| size_t number_length = remainder.size() - tail.size(); | |
| llvm::StringRef number = remainder.take_front(number_length); | |
| remainder = remainder.drop_front(number_length); | |
| return number; | |
| } | |
| return std::nullopt; | |
| } | |
| static llvm::Error IsNotAllowedByMode(llvm::StringRef expr, Token token, | |
| lldb::DILMode mode) { | |
| switch (mode) { | |
| case lldb::eDILModeSimple: | |
| if (!token.IsOneOf({Token::identifier, Token::period, Token::eof})) { | |
| return llvm::make_error<DILDiagnosticError>( | |
| expr, llvm::formatv("{0} is not allowed in DIL simple mode", token), | |
| token.GetLocation()); | |
| } | |
| break; | |
| case lldb::eDILModeLegacy: | |
| if (!token.IsOneOf({Token::identifier, Token::integer_constant, | |
| Token::period, Token::arrow, Token::star, Token::amp, | |
| Token::l_square, Token::r_square, Token::eof})) { | |
| return llvm::make_error<DILDiagnosticError>( | |
| expr, llvm::formatv("{0} is not allowed in DIL legacy mode", token), | |
| token.GetLocation()); | |
| } | |
| break; | |
| case lldb::eDILModeFull: | |
| break; | |
| } | |
| return llvm::Error::success(); | |
| } | |
| llvm::Expected<DILLexer> DILLexer::Create(llvm::StringRef expr, | |
| lldb::DILMode mode) { | |
| std::vector<Token> tokens; | |
| llvm::StringRef remainder = expr; | |
| do { | |
| if (llvm::Expected<Token> t = Lex(expr, remainder)) { | |
| Token token = *t; | |
| if (llvm::Error error = IsNotAllowedByMode(expr, token, mode)) { | |
| LLDB_LOG(GetLog(LLDBLog::Expressions), | |
| "[DILLexer::Create] DIL mode restriction:\n{0}", | |
| llvm::toStringWithoutConsuming(error)); | |
| return error; | |
| } | |
| tokens.push_back(std::move(token)); | |
| } else { | |
| auto error = t.takeError(); | |
| LLDB_LOG(GetLog(LLDBLog::Expressions), | |
| "[DILLexer::Create] DIL lexer failed:\n{0}", | |
| llvm::toStringWithoutConsuming(error)); | |
| return error; | |
| } | |
| } while (tokens.back().GetKind() != Token::eof); | |
| return DILLexer(expr, std::move(tokens)); | |
| } | |
| llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr, | |
| llvm::StringRef &remainder) { | |
| // Skip over whitespace (spaces). | |
| remainder = remainder.ltrim(); | |
| llvm::StringRef::iterator cur_pos = remainder.begin(); | |
| // Check to see if we've reached the end of our input string. | |
| if (remainder.empty()) | |
| return Token(Token::eof, "", (uint32_t)expr.size()); | |
| uint32_t position = cur_pos - expr.begin(); | |
| bool isFloat = false; | |
| std::optional<llvm::StringRef> maybe_number = IsNumber(remainder, isFloat); | |
| if (maybe_number) { | |
| auto kind = isFloat ? Token::float_constant : Token::integer_constant; | |
| return Token(kind, maybe_number->str(), position); | |
| } | |
| std::optional<llvm::StringRef> maybe_word = IsWord(expr, remainder); | |
| if (maybe_word) { | |
| llvm::StringRef word = *maybe_word; | |
| Token::Kind kind = llvm::StringSwitch<Token::Kind>(word) | |
| .Case("false", Token::kw_false) | |
| .Case("true", Token::kw_true) | |
| .Default(Token::identifier); | |
| return Token(kind, word.str(), position); | |
| } | |
| // IMPORTANT: If two or more tokens share the same prefix, the tokens need to | |
| // be ordered longest-to-shortest in the list below. E.g. '::' must come | |
| // before ':', and '+=' must come before '+'. | |
| constexpr std::pair<Token::Kind, const char *> operators[] = { | |
| {Token::greatergreaterequal, ">>="}, | |
| {Token::lesslessequal, "<<="}, | |
| {Token::ampamp, "&&"}, | |
| {Token::ampequal, "&="}, | |
| {Token::arrow, "->"}, | |
| {Token::caretequal, "^="}, | |
| {Token::coloncolon, "::"}, | |
| {Token::equalequal, "=="}, | |
| {Token::exclaimequal, "!="}, | |
| {Token::greaterequal, ">="}, | |
| {Token::greatergreater, ">>"}, | |
| {Token::lessequal, "<="}, | |
| {Token::lessless, "<<"}, | |
| {Token::minusequal, "-="}, | |
| {Token::minusminus, "--"}, | |
| {Token::percentequal, "%="}, | |
| {Token::pipeequal, "|="}, | |
| {Token::pipepipe, "||"}, | |
| {Token::plusequal, "+="}, | |
| {Token::plusplus, "++"}, | |
| {Token::slashequal, "/="}, | |
| {Token::starequal, "*="}, | |
| {Token::amp, "&"}, | |
| {Token::caret, "^"}, | |
| {Token::colon, ":"}, | |
| {Token::equal, "="}, | |
| {Token::exclaim, "!"}, | |
| {Token::greater, ">"}, | |
| {Token::l_paren, "("}, | |
| {Token::l_square, "["}, | |
| {Token::less, "<"}, | |
| {Token::minus, "-"}, | |
| {Token::percent, "%"}, | |
| {Token::period, "."}, | |
| {Token::pipe, "|"}, | |
| {Token::plus, "+"}, | |
| {Token::question, "?"}, | |
| {Token::r_paren, ")"}, | |
| {Token::r_square, "]"}, | |
| {Token::slash, "/"}, | |
| {Token::star, "*"}, | |
| {Token::tilde, "~"}, | |
| }; | |
| for (auto [kind, str] : operators) { | |
| if (remainder.consume_front(str)) | |
| return Token(kind, str, position); | |
| } | |
| // Unrecognized character(s) in string; unable to lex it. | |
| return llvm::make_error<DILDiagnosticError>(expr, "unrecognized token", | |
| position); | |
| } | |
| } // namespace lldb_private::dil |