OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "tools/gn/string_utils.h" | |
6 | |
7 #include "tools/gn/err.h" | |
8 #include "tools/gn/scope.h" | |
9 #include "tools/gn/token.h" | |
10 #include "tools/gn/tokenizer.h" | |
11 #include "tools/gn/value.h" | |
12 | |
13 namespace { | |
14 | |
15 // Constructs an Err indicating a range inside a string. We assume that the | |
16 // token has quotes around it that are not counted by the offset. | |
17 Err ErrInsideStringToken(const Token& token, size_t offset, size_t size, | |
18 const std::string& msg, | |
19 const std::string& help = std::string()) { | |
20 // The "+1" is skipping over the " at the beginning of the token. | |
21 Location begin_loc(token.location().file(), | |
22 token.location().line_number(), | |
23 token.location().char_offset() + offset + 1); | |
24 Location end_loc(token.location().file(), | |
25 token.location().line_number(), | |
26 token.location().char_offset() + offset + 1 + size); | |
27 return Err(LocationRange(begin_loc, end_loc), msg, help); | |
28 } | |
29 | |
30 // Given the character input[i] indicating the $ in a string, locates the | |
31 // identifier and places its range in |*identifier|, and updates |*i| to | |
32 // point to the last character consumed. | |
33 // | |
34 // On error returns false and sets the error. | |
35 bool LocateInlineIdenfitier(const Token& token, | |
36 const char* input, size_t size, | |
37 size_t* i, | |
38 base::StringPiece* identifier, | |
39 Err* err) { | |
40 size_t dollars_index = *i; | |
41 (*i)++; | |
42 if (*i == size) { | |
43 *err = ErrInsideStringToken(token, dollars_index, 1, "$ at end of string.", | |
44 "I was expecting an identifier after the $."); | |
45 return false; | |
46 } | |
47 | |
48 bool has_brackets; | |
49 if (input[*i] == '{') { | |
50 (*i)++; | |
51 if (*i == size) { | |
52 *err = ErrInsideStringToken(token, dollars_index, 2, | |
53 "${ at end of string.", | |
54 "I was expecting an identifier inside the ${...}."); | |
55 return false; | |
56 } | |
57 has_brackets = true; | |
58 } else { | |
59 has_brackets = false; | |
60 } | |
61 | |
62 // First char is special. | |
63 if (!Tokenizer::IsIdentifierFirstChar(input[*i])) { | |
64 *err = ErrInsideStringToken( | |
65 token, dollars_index, *i - dollars_index + 1, | |
66 "$ not followed by an identifier char.", | |
67 "It you want a literal $ use \"\\$\"."); | |
68 return false; | |
69 } | |
70 size_t begin_offset = *i; | |
71 (*i)++; | |
72 | |
73 // Find the first non-identifier char following the string. | |
74 while (*i < size && Tokenizer::IsIdentifierContinuingChar(input[*i])) | |
75 (*i)++; | |
76 size_t end_offset = *i; | |
77 | |
78 // If we started with a bracket, validate that there's an ending one. Leave | |
79 // *i pointing to the last char we consumed (backing up one). | |
80 if (has_brackets) { | |
81 if (*i == size) { | |
82 *err = ErrInsideStringToken(token, dollars_index, *i - dollars_index, | |
83 "Unterminated ${..."); | |
84 return false; | |
85 } else if (input[*i] != '}') { | |
86 *err = ErrInsideStringToken(token, *i, 1, "Not an identifier in string exp
ansion.", | |
87 "The contents of ${...} should be an identifier. " | |
88 "This character is out of sorts."); | |
89 return false; | |
90 } | |
91 // We want to consume the bracket but also back up one, so *i is unchanged. | |
92 } else { | |
93 (*i)--; | |
94 } | |
95 | |
96 *identifier = base::StringPiece(&input[begin_offset], | |
97 end_offset - begin_offset); | |
98 return true; | |
99 } | |
100 | |
101 bool AppendIdentifierValue(Scope* scope, | |
102 const Token& token, | |
103 const base::StringPiece& identifier, | |
104 std::string* output, | |
105 Err* err) { | |
106 const Value* value = scope->GetValue(identifier, true); | |
107 if (!value) { | |
108 // We assume the identifier points inside the token. | |
109 *err = ErrInsideStringToken( | |
110 token, identifier.data() - token.value().data() - 1, identifier.size(), | |
111 "Undefined identifier in string expansion.", | |
112 std::string("\"") + identifier + "\" is not currently in scope."); | |
113 return false; | |
114 } | |
115 | |
116 output->append(value->ToString()); | |
117 return true; | |
118 } | |
119 | |
120 } // namespace | |
121 | |
122 bool ExpandStringLiteral(Scope* scope, | |
123 const Token& literal, | |
124 Value* result, | |
125 Err* err) { | |
126 DCHECK(literal.type() == Token::STRING); | |
127 DCHECK(literal.value().size() > 1); // Should include quotes. | |
128 DCHECK(result->type() == Value::STRING); // Should be already set. | |
129 | |
130 // The token includes the surrounding quotes, so strip those off. | |
131 const char* input = &literal.value().data()[1]; | |
132 size_t size = literal.value().size() - 2; | |
133 | |
134 std::string& output = result->string_value(); | |
135 output.reserve(size); | |
136 for (size_t i = 0; i < size; i++) { | |
137 if (input[i] == '\\') { | |
138 if (i < size - 1) { | |
139 switch (input[i + 1]) { | |
140 case '\\': | |
141 case '"': | |
142 case '$': | |
143 output.push_back(input[i + 1]); | |
144 i++; | |
145 continue; | |
146 default: // Everything else has no meaning: pass the literal. | |
147 break; | |
148 } | |
149 } | |
150 output.push_back(input[i]); | |
151 } else if (input[i] == '$') { | |
152 base::StringPiece identifier; | |
153 if (!LocateInlineIdenfitier(literal, input, size, &i, &identifier, err)) | |
154 return false; | |
155 if (!AppendIdentifierValue(scope, literal, identifier, &output, err)) | |
156 return false; | |
157 } else { | |
158 output.push_back(input[i]); | |
159 } | |
160 } | |
161 return true; | |
162 } | |
163 | |
164 std::string RemovePrefix(const std::string& str, const std::string& prefix) { | |
165 CHECK(str.size() >= prefix.size() && | |
166 str.compare(0, prefix.size(), prefix) == 0); | |
167 return str.substr(prefix.size()); | |
168 } | |
OLD | NEW |