GN: Allow escape codes to be embedded in strings via $0xFF I've wanted to use them when writing assert messages. Might also be useful in write_file(). BUG= Review URL: https://codereview.chromium.org/1546393002 Cr-Original-Commit-Position: refs/heads/master@{#367622} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: f59e82f6169caace177e452c06377b3d611ed978
diff --git a/tools/gn/parser.cc b/tools/gn/parser.cc index 79966b8..d3e2f0f 100644 --- a/tools/gn/parser.cc +++ b/tools/gn/parser.cc
@@ -62,7 +62,8 @@ " escape = `\\` ( \"$\" | `\"` | char ) .\n" " BracketExpansion = \"{\" ( identifier | ArrayAccess | ScopeAccess " ") \"}\" .\n" - " expansion = \"$\" ( identifier | BracketExpansion ) .\n" + " Hex = \"0x\" [0-9A-Fa-f][0-9A-Fa-f]\n" + " expansion = \"$\" ( identifier | BracketExpansion | Hex ) .\n" " char = /* any character except \"$\", `\"`, or newline " "*/ .\n" "\n" @@ -74,6 +75,9 @@ "\n" " All other backslashes represent themselves.\n" "\n" + " To insert an arbitrary byte value, use $0xFF. For example, to\n" + " insert a newline character: \"Line one$0x0ALine two\".\n" + "\n" "Punctuation\n" "\n" " The following character sequences represent punctuation:\n"
diff --git a/tools/gn/string_utils.cc b/tools/gn/string_utils.cc index 5ee12d9..7246f75 100644 --- a/tools/gn/string_utils.cc +++ b/tools/gn/string_utils.cc
@@ -5,7 +5,9 @@ #include "tools/gn/string_utils.h" #include <stddef.h> +#include <cctype> +#include "base/strings/string_number_conversions.h" #include "tools/gn/err.h" #include "tools/gn/input_file.h" #include "tools/gn/parser.h" @@ -131,7 +133,7 @@ // Handles string interpolations: $identifier and ${expression} // -// |*i| is the index into |input| of the $. This will be updated to point to +// |*i| is the index into |input| after the $. This will be updated to point to // the last character consumed on success. The token is the original string // to blame on failure. // @@ -143,13 +145,7 @@ size_t* i, std::string* output, Err* err) { - size_t dollars_index = *i; - (*i)++; - if (*i == size) { - *err = ErrInsideStringToken(token, dollars_index, 1, "$ at end of string.", - "I was expecting an identifier or {...} after the $."); - return false; - } + size_t dollars_index = *i - 1; if (input[*i] == '{') { // Bracketed expression. @@ -203,6 +199,40 @@ end_offset, output, err); } +// Handles a hex literal: $0xFF +// +// |*i| is the index into |input| after the $. This will be updated to point to +// the last character consumed on success. The token is the original string +// to blame on failure. +// +// On failure, returns false and sets the error. On success, appends the +// char with the given hex value to |*output|. +bool AppendHexByte(Scope* scope, + const Token& token, + const char* input, size_t size, + size_t* i, + std::string* output, + Err* err) { + size_t dollars_index = *i - 1; + // "$0" is already known to exist. + if (*i + 3 >= size || input[*i + 1] != 'x' || !std::isxdigit(input[*i + 2]) || + !std::isxdigit(input[*i + 3])) { + *err = ErrInsideStringToken( + token, dollars_index, *i - dollars_index + 1, + "Invalid hex character. Hex values must look like 0xFF."); + return false; + } + int value = 0; + if (!base::HexStringToInt(base::StringPiece(&input[*i + 2], 2), &value)) { + *err = ErrInsideStringToken(token, dollars_index, *i - dollars_index + 1, + "Could not convert hex value."); + return false; + } + *i += 3; + output->push_back(value); + return true; +} + } // namespace bool ExpandStringLiteral(Scope* scope, @@ -235,7 +265,16 @@ } output.push_back(input[i]); } else if (input[i] == '$') { - if (!AppendStringInterpolation(scope, literal, input, size, &i, + i++; + if (i == size) { + *err = ErrInsideStringToken(literal, i - 1, 1, "$ at end of string.", + "I was expecting an identifier, 0xFF, or {...} after the $."); + return false; + } + if (input[i] == '0') { + if (!AppendHexByte(scope, literal, input, size, &i, &output, err)) + return false; + } else if (!AppendStringInterpolation(scope, literal, input, size, &i, &output, err)) return false; } else {
diff --git a/tools/gn/string_utils_unittest.cc b/tools/gn/string_utils_unittest.cc index 4e18eb5..d62e17e 100644 --- a/tools/gn/string_utils_unittest.cc +++ b/tools/gn/string_utils_unittest.cc
@@ -69,6 +69,10 @@ EXPECT_TRUE(CheckExpansionCase("$onescope", "{\n one = 1\n}", true)); EXPECT_TRUE(CheckExpansionCase("$onelist", "[1]", true)); + // Hex values + EXPECT_TRUE(CheckExpansionCase("$0x0AA", "\x0A""A", true)); + EXPECT_TRUE(CheckExpansionCase("$0x0a$0xfF", "\x0A\xFF", true)); + // Errors EXPECT_TRUE(CheckExpansionCase("hello #$", nullptr, false)); EXPECT_TRUE(CheckExpansionCase("hello #$%", nullptr, false)); @@ -76,6 +80,12 @@ EXPECT_TRUE(CheckExpansionCase("hello #${}", nullptr, false)); EXPECT_TRUE(CheckExpansionCase("hello #$nonexistant", nullptr, false)); EXPECT_TRUE(CheckExpansionCase("hello #${unterminated", nullptr, false)); + EXPECT_TRUE(CheckExpansionCase("hex truncated: $0", nullptr, false)); + EXPECT_TRUE(CheckExpansionCase("hex truncated: $0x", nullptr, false)); + EXPECT_TRUE(CheckExpansionCase("hex truncated: $0x0", nullptr, false)); + EXPECT_TRUE(CheckExpansionCase("hex with bad char: $0a", nullptr, false)); + EXPECT_TRUE(CheckExpansionCase("hex with bad char: $0x1z", nullptr, false)); + EXPECT_TRUE(CheckExpansionCase("hex with bad char: $0xz1", nullptr, false)); // Unknown backslash values aren't special. EXPECT_TRUE(CheckExpansionCase("\\", "\\", true));