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 13namespace { 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. 17Err 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. 35bool 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 expansion.", 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 101bool 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 122bool 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 164std::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} 169