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));