LLDB mainline
DILLexer.cpp
Go to the documentation of this file.
1//===-- DILLexer.cpp ------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7// This implements the recursive descent parser for the Data Inspection
8// Language (DIL), and its helper functions, which will eventually underlie the
9// 'frame variable' command. The language that this parser recognizes is
10// described in lldb/docs/dil-expr-lang.ebnf
11//
12//===----------------------------------------------------------------------===//
13
16#include "lldb/Utility/Status.h"
18#include "llvm/ADT/StringSwitch.h"
19
20namespace lldb_private::dil {
21
22llvm::StringRef Token::GetTokenName(Kind kind) {
23 switch (kind) {
24 case Kind::amp:
25 return "amp";
26 case Kind::ampamp:
27 return "ampamp";
28 case Kind::ampequal:
29 return "ampequal";
30 case Kind::arrow:
31 return "arrow";
32 case Kind::caret:
33 return "caret";
35 return "caretequal";
36 case Kind::colon:
37 return "colon";
39 return "coloncolon";
40 case Kind::equal:
41 return "equal";
42 case Kind::exclaim:
43 return "exclaim";
44 case Kind::eof:
45 return "eof";
47 return "equalequal";
49 return "exclaimequal";
51 return "float_constant";
52 case Kind::greater:
53 return "greater";
55 return "greaterequal";
57 return "greatergreater";
59 return "greatergreaterequal";
61 return "identifier";
63 return "integer_constant";
64 case Kind::kw_false:
65 return "false";
66 case Kind::kw_true:
67 return "true";
68 case Kind::l_paren:
69 return "l_paren";
70 case Kind::l_square:
71 return "l_square";
72 case Kind::less:
73 return "less";
74 case Kind::lessequal:
75 return "lessequal";
76 case Kind::lessless:
77 return "lessless";
79 return "lesslessequal";
80 case Kind::minus:
81 return "minus";
83 return "minusequal";
85 return "minusminus";
86 case Token::percent:
87 return "percent";
89 return "percentequal";
90 case Kind::period:
91 return "period";
92 case Kind::pipe:
93 return "pipe";
94 case Kind::pipeequal:
95 return "pipeequal";
96 case Kind::pipepipe:
97 return "pipepipe";
98 case Kind::plus:
99 return "plus";
100 case Kind::plusequal:
101 return "plusequal";
102 case Kind::plusplus:
103 return "plusplus";
104 case Kind::question:
105 return "question";
106 case Kind::r_paren:
107 return "r_paren";
108 case Kind::r_square:
109 return "r_square";
110 case Token::slash:
111 return "slash";
113 return "slashequal";
114 case Token::star:
115 return "star";
116 case Token::starequal:
117 return "starequal";
118 case Token::tilde:
119 return "tilde";
120 }
121 llvm_unreachable("Unknown token name");
122}
123
124static bool IsLetter(char c) {
125 return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
126}
127
128static bool IsDigit(char c) { return '0' <= c && c <= '9'; }
129
130// A word starts with a letter, underscore, or dollar sign, followed by
131// letters ('a'..'z','A'..'Z'), digits ('0'..'9'), and/or underscores.
132static std::optional<llvm::StringRef> IsWord(llvm::StringRef expr,
133 llvm::StringRef &remainder) {
134 // Find the longest prefix consisting of letters, digits, underscors and
135 // '$'. If it doesn't start with a digit, then it's a word.
136 llvm::StringRef candidate = remainder.take_while(
137 [](char c) { return IsDigit(c) || IsLetter(c) || c == '_' || c == '$'; });
138 if (candidate.empty() || IsDigit(candidate[0]))
139 return std::nullopt;
140 remainder = remainder.drop_front(candidate.size());
141 return candidate;
142}
143
144static bool IsNumberBodyChar(char ch) {
145 return IsDigit(ch) || IsLetter(ch) || ch == '.';
146}
147
148static std::optional<llvm::StringRef> IsNumber(llvm::StringRef &remainder,
149 bool &isFloat) {
150 llvm::StringRef tail = remainder;
151 llvm::StringRef body = tail.take_while(IsNumberBodyChar);
152 size_t dots = body.count('.');
153 if (dots > 1 || dots == body.size())
154 return std::nullopt;
155 if (IsDigit(body.front()) || (body[0] == '.' && IsDigit(body[1]))) {
156 isFloat = dots == 1;
157 tail = tail.drop_front(body.size());
158 bool isHex = body.contains_insensitive('x');
159 bool hasExp = !isHex && body.contains_insensitive('e');
160 bool hasHexExp = isHex && body.contains_insensitive('p');
161 if (hasExp || hasHexExp) {
162 isFloat = true; // This marks numbers like 0x1p1 and 1e1 as float
163 if (body.ends_with_insensitive("e") || body.ends_with_insensitive("p"))
164 if (tail.consume_front("+") || tail.consume_front("-"))
165 tail = tail.drop_while(IsNumberBodyChar);
166 }
167 size_t number_length = remainder.size() - tail.size();
168 llvm::StringRef number = remainder.take_front(number_length);
169 remainder = remainder.drop_front(number_length);
170 return number;
171 }
172 return std::nullopt;
173}
174
175static llvm::Error IsNotAllowedByMode(llvm::StringRef expr, Token token,
176 lldb::DILMode mode) {
177 switch (mode) {
179 if (!token.IsOneOf({Token::identifier, Token::period, Token::eof})) {
180 return llvm::make_error<DILDiagnosticError>(
181 expr, llvm::formatv("{0} is not allowed in DIL simple mode", token),
182 token.GetLocation());
183 }
184 break;
186 if (!token.IsOneOf({Token::identifier, Token::integer_constant,
187 Token::period, Token::arrow, Token::star, Token::amp,
188 Token::l_square, Token::r_square, Token::eof})) {
189 return llvm::make_error<DILDiagnosticError>(
190 expr, llvm::formatv("{0} is not allowed in DIL legacy mode", token),
191 token.GetLocation());
192 }
193 break;
195 break;
196 }
197 return llvm::Error::success();
198}
199
200llvm::Expected<DILLexer> DILLexer::Create(llvm::StringRef expr,
201 lldb::DILMode mode) {
202 std::vector<Token> tokens;
203 llvm::StringRef remainder = expr;
204 do {
205 if (llvm::Expected<Token> t = Lex(expr, remainder)) {
206 Token token = *t;
207 if (llvm::Error error = IsNotAllowedByMode(expr, token, mode)) {
209 "[DILLexer::Create] DIL mode restriction:\n{0}",
210 llvm::toStringWithoutConsuming(error));
211 return error;
212 }
213 tokens.push_back(std::move(token));
214 } else {
215 auto error = t.takeError();
217 "[DILLexer::Create] DIL lexer failed:\n{0}",
218 llvm::toStringWithoutConsuming(error));
219 return error;
220 }
221 } while (tokens.back().GetKind() != Token::eof);
222 return DILLexer(expr, std::move(tokens));
223}
224
225llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr,
226 llvm::StringRef &remainder) {
227 // Skip over whitespace (spaces).
228 remainder = remainder.ltrim();
229 llvm::StringRef::iterator cur_pos = remainder.begin();
230
231 // Check to see if we've reached the end of our input string.
232 if (remainder.empty())
233 return Token(Token::eof, "", (uint32_t)expr.size());
234
235 uint32_t position = cur_pos - expr.begin();
236 bool isFloat = false;
237 std::optional<llvm::StringRef> maybe_number = IsNumber(remainder, isFloat);
238 if (maybe_number) {
239 auto kind = isFloat ? Token::float_constant : Token::integer_constant;
240 return Token(kind, maybe_number->str(), position);
241 }
242 std::optional<llvm::StringRef> maybe_word = IsWord(expr, remainder);
243 if (maybe_word) {
244 llvm::StringRef word = *maybe_word;
245 Token::Kind kind = llvm::StringSwitch<Token::Kind>(word)
246 .Case("false", Token::kw_false)
247 .Case("true", Token::kw_true)
248 .Default(Token::identifier);
249 return Token(kind, word.str(), position);
250 }
251
252 // IMPORTANT: If two or more tokens share the same prefix, the tokens need to
253 // be ordered longest-to-shortest in the list below. E.g. '::' must come
254 // before ':', and '+=' must come before '+'.
255 constexpr std::pair<Token::Kind, const char *> operators[] = {
257 {Token::lesslessequal, "<<="},
258 {Token::ampamp, "&&"},
259 {Token::ampequal, "&="},
260 {Token::arrow, "->"},
261 {Token::caretequal, "^="},
262 {Token::coloncolon, "::"},
263 {Token::equalequal, "=="},
264 {Token::exclaimequal, "!="},
265 {Token::greaterequal, ">="},
266 {Token::greatergreater, ">>"},
267 {Token::lessequal, "<="},
268 {Token::lessless, "<<"},
269 {Token::minusequal, "-="},
270 {Token::minusminus, "--"},
271 {Token::percentequal, "%="},
272 {Token::pipeequal, "|="},
273 {Token::pipepipe, "||"},
274 {Token::plusequal, "+="},
275 {Token::plusplus, "++"},
276 {Token::slashequal, "/="},
277 {Token::starequal, "*="},
278 {Token::amp, "&"},
279 {Token::caret, "^"},
280 {Token::colon, ":"},
281 {Token::equal, "="},
282 {Token::exclaim, "!"},
283 {Token::greater, ">"},
284 {Token::l_paren, "("},
285 {Token::l_square, "["},
286 {Token::less, "<"},
287 {Token::minus, "-"},
288 {Token::percent, "%"},
289 {Token::period, "."},
290 {Token::pipe, "|"},
291 {Token::plus, "+"},
292 {Token::question, "?"},
293 {Token::r_paren, ")"},
294 {Token::r_square, "]"},
295 {Token::slash, "/"},
296 {Token::star, "*"},
297 {Token::tilde, "~"},
298 };
299 for (auto [kind, str] : operators) {
300 if (remainder.consume_front(str))
301 return Token(kind, str, position);
302 }
303
304 // Unrecognized character(s) in string; unable to lex it.
305 return llvm::make_error<DILDiagnosticError>(expr, "unrecognized token",
306 position);
307}
308
309} // namespace lldb_private::dil
static llvm::raw_ostream & error(Stream &strm)
#define LLDB_LOG(log,...)
The LLDB_LOG* macros defined below are the way to emit log messages.
Definition Log.h:375
static llvm::Expected< DILLexer > Create(llvm::StringRef expr, lldb::DILMode mode=lldb::eDILModeFull)
Lexes all the tokens in expr and calls the private constructor with the lexed tokens.
Definition DILLexer.cpp:200
DILLexer(llvm::StringRef dil_expr, std::vector< Token > lexed_tokens)
Definition DILLexer.h:147
static llvm::Expected< Token > Lex(llvm::StringRef expr, llvm::StringRef &remainder)
Definition DILLexer.cpp:225
Class defining the tokens generated by the DIL lexer and used by the DIL parser.
Definition DILLexer.h:25
static llvm::StringRef GetTokenName(Kind kind)
Definition DILLexer.cpp:22
uint32_t GetLocation() const
Definition DILLexer.h:93
bool IsOneOf(llvm::ArrayRef< Kind > kinds) const
Definition DILLexer.h:89
static std::optional< llvm::StringRef > IsWord(llvm::StringRef expr, llvm::StringRef &remainder)
Definition DILLexer.cpp:132
static bool IsNumberBodyChar(char ch)
Definition DILLexer.cpp:144
static llvm::Error IsNotAllowedByMode(llvm::StringRef expr, Token token, lldb::DILMode mode)
Definition DILLexer.cpp:175
static bool IsLetter(char c)
Definition DILLexer.cpp:124
static std::optional< llvm::StringRef > IsNumber(llvm::StringRef &remainder, bool &isFloat)
Definition DILLexer.cpp:148
static bool IsDigit(char c)
Definition DILLexer.cpp:128
Log * GetLog(Cat mask)
Retrieve the Log object for the channel associated with the given log enum.
Definition Log.h:338
DILMode
Data Inspection Language (DIL) evaluation modes.
@ eDILModeFull
Allowed: everything supported by DIL.
@ eDILModeLegacy
Allowed: identifiers, integers, operators: '.', '->', '*', '&', '[]'.
@ eDILModeSimple
Allowed: identifiers, operators: '.'.