blob: 35c14fab127bd7f4bb6e2b3e93aec5c22fd551ab [file] [log] [blame]
// Copyright 2014 The Chromium 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 "gn/substitution_pattern.h"
#include <stddef.h>
#include "base/strings/string_number_conversions.h"
#include "gn/build_settings.h"
#include "gn/err.h"
#include "gn/filesystem_utils.h"
#include "gn/value.h"
SubstitutionPattern::Subrange::Subrange() : type(&SubstitutionLiteral) {}
SubstitutionPattern::Subrange::Subrange(const Substitution* t,
const std::string& l)
: type(t), literal(l) {}
SubstitutionPattern::Subrange::~Subrange() = default;
SubstitutionPattern::SubstitutionPattern() : origin_(nullptr) {}
SubstitutionPattern::SubstitutionPattern(const SubstitutionPattern& other) =
default;
SubstitutionPattern::~SubstitutionPattern() = default;
bool SubstitutionPattern::Parse(const Value& value, Err* err) {
if (!value.VerifyTypeIs(Value::STRING, err))
return false;
return Parse(value.string_value(), value.origin(), err);
}
bool SubstitutionPattern::Parse(const std::string& str,
const ParseNode* origin,
Err* err) {
DCHECK(ranges_.empty()); // Should only be called once.
size_t cur = 0;
while (true) {
size_t next = str.find("{{", cur);
// Pick up everything from the previous spot to here as a literal.
if (next == std::string::npos) {
if (cur != str.size())
ranges_.push_back(Subrange(&SubstitutionLiteral, str.substr(cur)));
break;
} else if (next > cur) {
ranges_.push_back(
Subrange(&SubstitutionLiteral, str.substr(cur, next - cur)));
}
// Find which specific pattern this corresponds to.
bool found_match = false;
for (const SubstitutionTypes* types : AllSubstitutions) {
for (const Substitution* sub : *types) {
const char* cur_pattern = sub->name;
size_t cur_len = strlen(cur_pattern);
if (str.compare(next, cur_len, cur_pattern) == 0) {
ranges_.push_back(Subrange(sub));
cur = next + cur_len;
found_match = true;
break;
}
}
}
// Expect all occurrences of {{ to resolve to a pattern.
if (!found_match) {
// Could make this error message more friendly if it comes up a lot. But
// most people will not be writing substitution patterns and the code
// to exactly indicate the error location is tricky.
*err = Err(origin, "Unknown substitution pattern",
"Found a {{ at offset " + base::NumberToString(next) +
" and did not find a known substitution following it.");
ranges_.clear();
return false;
}
}
origin_ = origin;
// Fill required types vector.
SubstitutionBits bits;
FillRequiredTypes(&bits);
bits.FillVector(&required_types_);
return true;
}
// static
SubstitutionPattern SubstitutionPattern::MakeForTest(const char* str) {
Err err;
SubstitutionPattern pattern;
CHECK(pattern.Parse(str, nullptr, &err))
<< err.message() << "\n" << err.help_text();
return pattern;
}
std::string SubstitutionPattern::AsString() const {
std::string result;
for (const auto& elem : ranges_) {
if (elem.type == &SubstitutionLiteral)
result.append(elem.literal);
else
result.append(elem.type->name);
}
return result;
}
void SubstitutionPattern::FillRequiredTypes(SubstitutionBits* bits) const {
for (const auto& elem : ranges_) {
if (elem.type != &SubstitutionLiteral)
bits->used.insert(elem.type);
}
}
bool SubstitutionPattern::IsInOutputDir(const BuildSettings* build_settings,
Err* err) const {
if (ranges_.empty()) {
*err = Err(origin_, "This is empty but I was expecting an output file.");
return false;
}
if (ranges_[0].type == &SubstitutionLiteral) {
// If the first thing is a literal, it must start with the output dir.
if (!EnsureStringIsInOutputDir(build_settings->build_dir(),
ranges_[0].literal, origin_, err))
return false;
} else {
// Otherwise, the first subrange must be a pattern that expands to
// something in the output directory.
if (!SubstitutionIsInOutputDir(ranges_[0].type)) {
*err =
Err(origin_, "File is not inside output directory.",
"The given file should be in the output directory. Normally you\n"
"would specify\n\"$target_out_dir/foo\" or "
"\"{{source_gen_dir}}/foo\".");
return false;
}
}
return true;
}