| // Copyright 2019 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/developer/debug/zxdb/expr/parse_special_identifier.h" | |
| #include <ctype.h> | |
| namespace zxdb { | |
| namespace { | |
| bool IsSpecialIdentifierChar(char c) { return isalnum(c) || c == '_'; } | |
| } // namespace | |
| Err ParseSpecialIdentifier(std::string_view input, size_t* cur, SpecialIdentifier* special, | |
| std::string* contents, size_t* error_location) { | |
| *special = SpecialIdentifier::kNone; | |
| contents->clear(); | |
| if (*cur >= input.size() || input[*cur] != '$') { | |
| *error_location = *cur; | |
| return Err("This is not a special identifier."); // This is really an internal error. | |
| } | |
| // Skip over the '$'. | |
| size_t special_name_begin = *cur; | |
| ++(*cur); | |
| // Extract and move over the special name. | |
| while (*cur < input.size() && IsSpecialIdentifierChar(input[*cur])) | |
| ++(*cur); | |
| std::string_view special_name = input.substr(special_name_begin, *cur - special_name_begin); | |
| *special = StringToSpecialIdentifier(special_name); | |
| if (*special == SpecialIdentifier::kNone) { | |
| *error_location = special_name_begin + 1; // Text after the "$". | |
| return Err("The string '" + std::string(special_name) + "' is not a valid special identifier."); | |
| } | |
| // A paren following the special name is the contents. | |
| if (*cur == input.size() || input[*cur] != '(') { | |
| // No contents. There has to be a special name in this case to prevent us from getting confused | |
| // by standalone "$". | |
| if (*special == SpecialIdentifier::kEscaped) { | |
| *error_location = *cur; | |
| return Err("Expected special name or '(' for escaped input."); | |
| } | |
| return Err(); | |
| } | |
| // Skip the paren. | |
| size_t open_paren_index = *cur; | |
| ++(*cur); | |
| int paren_depth = 0; | |
| // Go through the contents of the (). Parens don't need escaping as long as they're matched, but | |
| // can be escaped with backslashes. | |
| while (*cur < input.size()) { | |
| if (input[*cur] == '(') { | |
| contents->push_back('('); | |
| paren_depth++; | |
| } else if (input[*cur] == ')') { | |
| // Match with other open parens we've encountered, or it indicates the end. | |
| if (paren_depth == 0) { | |
| ++(*cur); // Skip over closing paren. | |
| return Err(); // Done. | |
| } | |
| paren_depth--; | |
| contents->push_back(')'); | |
| } else if (input[*cur] == '\\') { | |
| // Backslash escapes. | |
| ++(*cur); // Skip over backslash. | |
| if (*cur == input.size()) | |
| break; // End-of-loop code handles this case. | |
| char escaped_char = input[*cur]; | |
| if (escaped_char == '\\' || escaped_char == '(' || escaped_char == ')') { | |
| // Only allow certain characters to be escaped. | |
| contents->push_back(escaped_char); | |
| } else { | |
| // Everything else is an error. | |
| *error_location = *cur; | |
| return Err("Invalid backslash-escaped character in special identifier."); | |
| } | |
| } else { | |
| // All other characters are literals. | |
| contents->push_back(input[*cur]); | |
| } | |
| ++(*cur); | |
| } | |
| // Error location indicates the opening paren which makes the error easier to understand. | |
| *error_location = open_paren_index; | |
| return Err("Unexpected end of input in special identifier to match."); | |
| } | |
| } // namespace zxdb |