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