1// Copyright 2014 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/substitution_pattern.h"
6
7#include "base/strings/string_number_conversions.h"
8#include "tools/gn/build_settings.h"
9#include "tools/gn/err.h"
10#include "tools/gn/filesystem_utils.h"
11#include "tools/gn/value.h"
12
13SubstitutionPattern::Subrange::Subrange()
14    : type(SUBSTITUTION_LITERAL) {
15}
16
17SubstitutionPattern::Subrange::Subrange(SubstitutionType t,
18                                        const std::string& l)
19    : type(t),
20      literal(l) {
21}
22
23SubstitutionPattern::Subrange::~Subrange() {
24}
25
26SubstitutionPattern::SubstitutionPattern() : origin_(NULL) {
27}
28
29SubstitutionPattern::~SubstitutionPattern() {
30}
31
32bool SubstitutionPattern::Parse(const Value& value, Err* err) {
33  if (!value.VerifyTypeIs(Value::STRING, err))
34    return false;
35  return Parse(value.string_value(), value.origin(), err);
36}
37
38bool SubstitutionPattern::Parse(const std::string& str,
39                                const ParseNode* origin,
40                                Err* err) {
41  DCHECK(ranges_.empty());  // Should only be called once.
42
43  size_t cur = 0;
44  while (true) {
45    size_t next = str.find("{{", cur);
46
47    // Pick up everything from the previous spot to here as a literal.
48    if (next == std::string::npos) {
49      if (cur != str.size())
50        ranges_.push_back(Subrange(SUBSTITUTION_LITERAL, str.substr(cur)));
51      break;
52    } else if (next > cur) {
53      ranges_.push_back(
54          Subrange(SUBSTITUTION_LITERAL, str.substr(cur, next - cur)));
55    }
56
57    // Find which specific pattern this corresponds to.
58    bool found_match = false;
59    for (size_t i = SUBSTITUTION_FIRST_PATTERN;
60         i < SUBSTITUTION_NUM_TYPES; i++) {
61      const char* cur_pattern = kSubstitutionNames[i];
62      size_t cur_len = strlen(cur_pattern);
63      if (str.compare(next, cur_len, cur_pattern) == 0) {
64        ranges_.push_back(Subrange(static_cast<SubstitutionType>(i)));
65        cur = next + cur_len;
66        found_match = true;
67        break;
68      }
69    }
70
71    // Expect all occurrances of {{ to resolve to a pattern.
72    if (!found_match) {
73      // Could make this error message more friendly if it comes up a lot. But
74      // most people will not be writing substitution patterns and the code
75      // to exactly indicate the error location is tricky.
76      *err = Err(origin, "Unknown substitution pattern",
77          "Found a {{ at offset " +
78          base::SizeTToString(next) +
79          " and did not find a known substitution following it.");
80      ranges_.clear();
81      return false;
82    }
83  }
84
85  origin_ = origin;
86
87  // Fill required types vector.
88  SubstitutionBits bits;
89  FillRequiredTypes(&bits);
90  bits.FillVector(&required_types_);
91  return true;
92}
93
94// static
95SubstitutionPattern SubstitutionPattern::MakeForTest(const char* str) {
96  Err err;
97  SubstitutionPattern pattern;
98  CHECK(pattern.Parse(str, NULL, &err)) << err.message();
99  return pattern;
100}
101
102std::string SubstitutionPattern::AsString() const {
103  std::string result;
104  for (size_t i = 0; i < ranges_.size(); i++) {
105    if (ranges_[i].type == SUBSTITUTION_LITERAL)
106      result.append(ranges_[i].literal);
107    else
108      result.append(kSubstitutionNames[ranges_[i].type]);
109  }
110  return result;
111}
112
113void SubstitutionPattern::FillRequiredTypes(SubstitutionBits* bits) const {
114  for (size_t i = 0; i < ranges_.size(); i++) {
115    if (ranges_[i].type != SUBSTITUTION_LITERAL)
116      bits->used[static_cast<size_t>(ranges_[i].type)] = true;
117  }
118}
119
120bool SubstitutionPattern::IsInOutputDir(const BuildSettings* build_settings,
121                                        Err* err) const {
122  if (ranges_.empty()) {
123    *err = Err(origin_, "This is empty but I was expecting an output file.");
124    return false;
125  }
126
127  if (ranges_[0].type == SUBSTITUTION_LITERAL) {
128    // If the first thing is a literal, it must start with the output dir.
129    if (!EnsureStringIsInOutputDir(
130            build_settings->build_dir(),
131            ranges_[0].literal, origin_, err))
132      return false;
133  } else {
134    // Otherwise, the first subrange must be a pattern that expands to
135    // something in the output directory.
136    if (!SubstitutionIsInOutputDir(ranges_[0].type)) {
137      *err = Err(origin_,
138          "File is not inside output directory.",
139          "The given file should be in the output directory. Normally you\n"
140          "would specify\n\"$target_out_dir/foo\" or "
141          "\"{{source_gen_dir}}/foo\".");
142      return false;
143    }
144  }
145
146  return true;
147}
148