/src/CMake/Source/cmList.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | |
4 | | #include "cmConfigure.h" // IWYU pragma: keep |
5 | | |
6 | | #include "cmList.h" |
7 | | |
8 | | #include <algorithm> |
9 | | #include <cstddef> |
10 | | #include <functional> |
11 | | #include <iterator> |
12 | | #include <set> |
13 | | #include <stdexcept> |
14 | | #include <utility> |
15 | | |
16 | | #include <cm/memory> |
17 | | |
18 | | #include "cmsys/RegularExpression.hxx" |
19 | | |
20 | | #include "cmAlgorithms.h" |
21 | | #include "cmExecutionStatus.h" |
22 | | #include "cmGeneratorExpression.h" |
23 | | #include "cmListFileCache.h" |
24 | | #include "cmMakefile.h" |
25 | | #include "cmRange.h" |
26 | | #include "cmState.h" |
27 | | #include "cmStringAlgorithms.h" |
28 | | #include "cmStringReplaceHelper.h" |
29 | | #include "cmSystemTools.h" |
30 | | #include "cmValue.h" |
31 | | |
32 | | cm::string_view cmList::element_separator{ ";" }; |
33 | | |
34 | | cmList cmList::sublist(size_type pos, size_type length) const |
35 | 0 | { |
36 | 0 | if (pos >= this->Values.size()) { |
37 | 0 | throw std::out_of_range(cmStrCat( |
38 | 0 | "begin index: ", pos, " is out of range 0 - ", this->Values.size() - 1)); |
39 | 0 | } |
40 | | |
41 | 0 | size_type count = (length == npos || pos + length > this->size()) |
42 | 0 | ? this->size() |
43 | 0 | : pos + length; |
44 | 0 | return this->sublist(this->begin() + pos, this->begin() + count); |
45 | 0 | } |
46 | | |
47 | | cmList::size_type cmList::find(cm::string_view value) const |
48 | 0 | { |
49 | 0 | auto res = std::find(this->Values.begin(), this->Values.end(), value); |
50 | 0 | if (res == this->Values.end()) { |
51 | 0 | return npos; |
52 | 0 | } |
53 | | |
54 | 0 | return std::distance(this->Values.begin(), res); |
55 | 0 | } |
56 | | |
57 | | cmList& cmList::remove_duplicates() |
58 | 0 | { |
59 | 0 | auto newEnd = cmRemoveDuplicates(this->Values); |
60 | 0 | this->Values.erase(newEnd, this->Values.end()); |
61 | |
|
62 | 0 | return *this; |
63 | 0 | } |
64 | | |
65 | | namespace { |
66 | | class MatchesRegex |
67 | | { |
68 | | public: |
69 | | MatchesRegex(cmsys::RegularExpression& regex, cmList::FilterMode mode) |
70 | 0 | : Regex(regex) |
71 | 0 | , IncludeMatches(mode == cmList::FilterMode::INCLUDE) |
72 | 0 | { |
73 | 0 | } |
74 | | |
75 | | bool operator()(std::string const& target) |
76 | 0 | { |
77 | 0 | return this->Regex.find(target) ^ this->IncludeMatches; |
78 | 0 | } |
79 | | |
80 | | private: |
81 | | cmsys::RegularExpression& Regex; |
82 | | bool const IncludeMatches; |
83 | | }; |
84 | | } |
85 | | |
86 | | cmList& cmList::filter(cm::string_view pattern, FilterMode mode) |
87 | 0 | { |
88 | 0 | cmsys::RegularExpression regex(std::string{ pattern }); |
89 | 0 | if (!regex.is_valid()) { |
90 | 0 | throw std::invalid_argument( |
91 | 0 | cmStrCat("sub-command FILTER, mode REGEX failed to compile regex \"", |
92 | 0 | pattern, "\".")); |
93 | 0 | } |
94 | | |
95 | 0 | auto it = std::remove_if(this->Values.begin(), this->Values.end(), |
96 | 0 | MatchesRegex{ regex, mode }); |
97 | 0 | this->Values.erase(it, this->Values.end()); |
98 | |
|
99 | 0 | return *this; |
100 | 0 | } |
101 | | |
102 | | namespace { |
103 | | class StringSorter |
104 | | { |
105 | | protected: |
106 | | using StringFilter = std::function<std::string(std::string const&)>; |
107 | | |
108 | | using OrderMode = cmList::SortConfiguration::OrderMode; |
109 | | using CompareMethod = cmList::SortConfiguration::CompareMethod; |
110 | | using CaseSensitivity = cmList::SortConfiguration::CaseSensitivity; |
111 | | |
112 | | StringFilter GetCompareFilter(CompareMethod compare) |
113 | 0 | { |
114 | 0 | return (compare == CompareMethod::FILE_BASENAME) |
115 | 0 | ? cmSystemTools::GetFilenameName |
116 | 0 | : nullptr; |
117 | 0 | } |
118 | | |
119 | | StringFilter GetCaseFilter(CaseSensitivity sensitivity) |
120 | 0 | { |
121 | 0 | return (sensitivity == CaseSensitivity::INSENSITIVE) |
122 | 0 | ? cmsys::SystemTools::LowerCase |
123 | 0 | : nullptr; |
124 | 0 | } |
125 | | |
126 | | using ComparisonFunction = |
127 | | std::function<bool(std::string const&, std::string const&)>; |
128 | | ComparisonFunction GetComparisonFunction(CompareMethod compare) |
129 | 0 | { |
130 | 0 | if (compare == CompareMethod::NATURAL) { |
131 | 0 | return std::function<bool(std::string const&, std::string const&)>( |
132 | 0 | [](std::string const& x, std::string const& y) { |
133 | 0 | return cmSystemTools::strverscmp(x, y) < 0; |
134 | 0 | }); |
135 | 0 | } |
136 | 0 | return std::function<bool(std::string const&, std::string const&)>( |
137 | 0 | [](std::string const& x, std::string const& y) { return x < y; }); |
138 | 0 | } |
139 | | |
140 | | public: |
141 | | StringSorter(cmList::SortConfiguration config) |
142 | 0 | : Filters{ this->GetCompareFilter(config.Compare), |
143 | 0 | this->GetCaseFilter(config.Case) } |
144 | 0 | , SortMethod(this->GetComparisonFunction(config.Compare)) |
145 | 0 | , Descending(config.Order == OrderMode::DESCENDING) |
146 | 0 | { |
147 | 0 | } |
148 | | |
149 | | std::string ApplyFilter(std::string const& argument) |
150 | 0 | { |
151 | 0 | std::string result = argument; |
152 | 0 | for (auto const& filter : this->Filters) { |
153 | 0 | if (filter) { |
154 | 0 | result = filter(result); |
155 | 0 | } |
156 | 0 | } |
157 | 0 | return result; |
158 | 0 | } |
159 | | |
160 | | bool operator()(std::string const& a, std::string const& b) |
161 | 0 | { |
162 | 0 | std::string af = this->ApplyFilter(a); |
163 | 0 | std::string bf = this->ApplyFilter(b); |
164 | 0 | bool result; |
165 | 0 | if (this->Descending) { |
166 | 0 | result = this->SortMethod(bf, af); |
167 | 0 | } else { |
168 | 0 | result = this->SortMethod(af, bf); |
169 | 0 | } |
170 | 0 | return result; |
171 | 0 | } |
172 | | |
173 | | private: |
174 | | StringFilter Filters[2] = { nullptr, nullptr }; |
175 | | ComparisonFunction SortMethod; |
176 | | bool Descending; |
177 | | }; |
178 | | } |
179 | | |
180 | 0 | cmList::SortConfiguration::SortConfiguration() = default; |
181 | | |
182 | | cmList& cmList::sort(SortConfiguration cfg) |
183 | 0 | { |
184 | 0 | SortConfiguration config{ cfg }; |
185 | |
|
186 | 0 | if (config.Order == SortConfiguration::OrderMode::DEFAULT) { |
187 | 0 | config.Order = SortConfiguration::OrderMode::ASCENDING; |
188 | 0 | } |
189 | 0 | if (config.Compare == SortConfiguration::CompareMethod::DEFAULT) { |
190 | 0 | config.Compare = SortConfiguration::CompareMethod::STRING; |
191 | 0 | } |
192 | 0 | if (config.Case == SortConfiguration::CaseSensitivity::DEFAULT) { |
193 | 0 | config.Case = SortConfiguration::CaseSensitivity::SENSITIVE; |
194 | 0 | } |
195 | |
|
196 | 0 | if ((config.Compare == SortConfiguration::CompareMethod::STRING) && |
197 | 0 | (config.Case == SortConfiguration::CaseSensitivity::SENSITIVE) && |
198 | 0 | (config.Order == SortConfiguration::OrderMode::ASCENDING)) { |
199 | 0 | std::sort(this->Values.begin(), this->Values.end()); |
200 | 0 | } else { |
201 | 0 | StringSorter sorter(config); |
202 | 0 | std::sort(this->Values.begin(), this->Values.end(), sorter); |
203 | 0 | } |
204 | |
|
205 | 0 | return *this; |
206 | 0 | } |
207 | | |
208 | | namespace { |
209 | | using transform_type = std::function<std::string(std::string const&)>; |
210 | | using transform_error = cmList::transform_error; |
211 | | |
212 | | class TransformSelector : public cmList::TransformSelector |
213 | | { |
214 | | public: |
215 | 0 | ~TransformSelector() override = default; |
216 | | |
217 | | std::string Tag; |
218 | | |
219 | 0 | std::string const& GetTag() override { return this->Tag; } |
220 | | |
221 | | virtual bool Validate(std::size_t count = 0) = 0; |
222 | | |
223 | | virtual bool InSelection(std::string const&) = 0; |
224 | | |
225 | | virtual void Transform(cmList::container_type& list, |
226 | | transform_type const& transform) |
227 | 0 | { |
228 | 0 | std::transform(list.begin(), list.end(), list.begin(), transform); |
229 | 0 | } |
230 | | |
231 | | protected: |
232 | | TransformSelector(std::string&& tag) |
233 | 0 | : Tag(std::move(tag)) |
234 | 0 | { |
235 | 0 | } |
236 | | }; |
237 | | |
238 | | class TransformNoSelector : public TransformSelector |
239 | | { |
240 | | public: |
241 | | TransformNoSelector() |
242 | 0 | : TransformSelector("NO SELECTOR") |
243 | 0 | { |
244 | 0 | } |
245 | | |
246 | 0 | bool Validate(std::size_t) override { return true; } |
247 | | |
248 | 0 | bool InSelection(std::string const&) override { return true; } |
249 | | }; |
250 | | class TransformSelectorRegex : public TransformSelector |
251 | | { |
252 | | public: |
253 | | TransformSelectorRegex(std::string const& regex) |
254 | 0 | : TransformSelector("REGEX") |
255 | 0 | , Regex(regex) |
256 | 0 | { |
257 | 0 | } |
258 | | TransformSelectorRegex(std::string&& regex) |
259 | 0 | : TransformSelector("REGEX") |
260 | 0 | , Regex(regex) |
261 | 0 | { |
262 | 0 | } |
263 | | |
264 | 0 | bool Validate(std::size_t) override { return this->Regex.is_valid(); } |
265 | | |
266 | | bool InSelection(std::string const& value) override |
267 | 0 | { |
268 | 0 | return this->Regex.find(value); |
269 | 0 | } |
270 | | |
271 | | cmsys::RegularExpression Regex; |
272 | | }; |
273 | | class TransformSelectorIndexes : public TransformSelector |
274 | | { |
275 | | public: |
276 | | std::vector<index_type> Indexes; |
277 | | |
278 | 0 | bool InSelection(std::string const&) override { return true; } |
279 | | |
280 | | void Transform(std::vector<std::string>& list, |
281 | | transform_type const& transform) override |
282 | 0 | { |
283 | 0 | this->Validate(list.size()); |
284 | |
|
285 | 0 | for (auto index : this->Indexes) { |
286 | 0 | list[index] = transform(list[index]); |
287 | 0 | } |
288 | 0 | } |
289 | | |
290 | | protected: |
291 | | TransformSelectorIndexes(std::string&& tag) |
292 | 0 | : TransformSelector(std::move(tag)) |
293 | 0 | { |
294 | 0 | } |
295 | | TransformSelectorIndexes(std::string&& tag, |
296 | | std::vector<index_type> const& indexes) |
297 | 0 | : TransformSelector(std::move(tag)) |
298 | 0 | , Indexes(indexes) |
299 | 0 | { |
300 | 0 | } |
301 | | TransformSelectorIndexes(std::string&& tag, |
302 | | std::vector<index_type>&& indexes) |
303 | 0 | : TransformSelector(std::move(tag)) |
304 | 0 | , Indexes(indexes) |
305 | 0 | { |
306 | 0 | } |
307 | | |
308 | | index_type NormalizeIndex(index_type index, std::size_t count) |
309 | 0 | { |
310 | 0 | if (index < 0) { |
311 | 0 | index = static_cast<index_type>(count) + index; |
312 | 0 | } |
313 | 0 | if (index < 0 || count <= static_cast<std::size_t>(index)) { |
314 | 0 | throw transform_error(cmStrCat( |
315 | 0 | "sub-command TRANSFORM, selector ", this->Tag, ", index: ", index, |
316 | 0 | " out of range (-", count, ", ", count - 1, ").")); |
317 | 0 | } |
318 | 0 | return index; |
319 | 0 | } |
320 | | }; |
321 | | class TransformSelectorAt : public TransformSelectorIndexes |
322 | | { |
323 | | public: |
324 | | TransformSelectorAt(std::vector<index_type> const& indexes) |
325 | 0 | : TransformSelectorIndexes("AT", indexes) |
326 | 0 | { |
327 | 0 | } |
328 | | TransformSelectorAt(std::vector<index_type>&& indexes) |
329 | 0 | : TransformSelectorIndexes("AT", std::move(indexes)) |
330 | 0 | { |
331 | 0 | } |
332 | | |
333 | | bool Validate(std::size_t count) override |
334 | 0 | { |
335 | 0 | decltype(this->Indexes) indexes; |
336 | |
|
337 | 0 | for (auto index : this->Indexes) { |
338 | 0 | indexes.push_back(this->NormalizeIndex(index, count)); |
339 | 0 | } |
340 | 0 | this->Indexes = std::move(indexes); |
341 | |
|
342 | 0 | return true; |
343 | 0 | } |
344 | | }; |
345 | | class TransformSelectorFor : public TransformSelectorIndexes |
346 | | { |
347 | | public: |
348 | | TransformSelectorFor(index_type start, index_type stop, index_type step) |
349 | 0 | : TransformSelectorIndexes("FOR") |
350 | 0 | , Start(start) |
351 | 0 | , Stop(stop) |
352 | 0 | , Step(step) |
353 | 0 | { |
354 | 0 | } |
355 | | |
356 | | bool Validate(std::size_t count) override |
357 | 0 | { |
358 | 0 | this->Start = this->NormalizeIndex(this->Start, count); |
359 | 0 | this->Stop = this->NormalizeIndex(this->Stop, count); |
360 | | |
361 | | // Does stepping move us further from the end? |
362 | 0 | if (this->Start > this->Stop) { |
363 | 0 | throw transform_error( |
364 | 0 | cmStrCat("sub-command TRANSFORM, selector FOR " |
365 | 0 | "expects <start> to be no greater than <stop> (", |
366 | 0 | this->Start, " > ", this->Stop, ')')); |
367 | 0 | } |
368 | | |
369 | | // compute indexes |
370 | 0 | auto size = (this->Stop - this->Start + 1) / this->Step; |
371 | 0 | if ((this->Stop - this->Start + 1) % this->Step != 0) { |
372 | 0 | size += 1; |
373 | 0 | } |
374 | |
|
375 | 0 | this->Indexes.resize(size); |
376 | 0 | auto start = this->Start; |
377 | 0 | auto step = this->Step; |
378 | 0 | std::generate(this->Indexes.begin(), this->Indexes.end(), |
379 | 0 | [&start, step]() -> index_type { |
380 | 0 | auto r = start; |
381 | 0 | start += step; |
382 | 0 | return r; |
383 | 0 | }); |
384 | |
|
385 | 0 | return true; |
386 | 0 | } |
387 | | |
388 | | private: |
389 | | index_type Start, Stop, Step; |
390 | | }; |
391 | | |
392 | | class TransformAction |
393 | | { |
394 | | public: |
395 | 0 | virtual ~TransformAction() = default; |
396 | | |
397 | 0 | void Initialize(TransformSelector* selector) { this->Selector = selector; } |
398 | 0 | virtual void Initialize(TransformSelector*, std::string const&) {} |
399 | | virtual void Initialize(TransformSelector*, std::string const&, |
400 | | std::string const&) |
401 | 0 | { |
402 | 0 | } |
403 | | virtual void Initialize(TransformSelector* selector, |
404 | | std::vector<std::string> const&) |
405 | 0 | { |
406 | 0 | this->Initialize(selector); |
407 | 0 | } |
408 | | |
409 | | virtual std::string operator()(std::string const& s) = 0; |
410 | | |
411 | | protected: |
412 | | TransformSelector* Selector; |
413 | | }; |
414 | | class TransformActionAppend : public TransformAction |
415 | | { |
416 | | public: |
417 | | using TransformAction::Initialize; |
418 | | |
419 | | void Initialize(TransformSelector* selector, |
420 | | std::string const& append) override |
421 | 0 | { |
422 | 0 | TransformAction::Initialize(selector); |
423 | 0 | this->Append = append; |
424 | 0 | } |
425 | | void Initialize(TransformSelector* selector, |
426 | | std::vector<std::string> const& append) override |
427 | 0 | { |
428 | 0 | this->Initialize(selector, append.front()); |
429 | 0 | } |
430 | | |
431 | | std::string operator()(std::string const& s) override |
432 | 0 | { |
433 | 0 | if (this->Selector->InSelection(s)) { |
434 | 0 | return cmStrCat(s, this->Append); |
435 | 0 | } |
436 | | |
437 | 0 | return s; |
438 | 0 | } |
439 | | |
440 | | private: |
441 | | std::string Append; |
442 | | }; |
443 | | class TransformActionPrepend : public TransformAction |
444 | | { |
445 | | public: |
446 | | using TransformAction::Initialize; |
447 | | |
448 | | void Initialize(TransformSelector* selector, |
449 | | std::string const& prepend) override |
450 | 0 | { |
451 | 0 | TransformAction::Initialize(selector); |
452 | 0 | this->Prepend = prepend; |
453 | 0 | } |
454 | | void Initialize(TransformSelector* selector, |
455 | | std::vector<std::string> const& prepend) override |
456 | 0 | { |
457 | 0 | this->Initialize(selector, prepend.front()); |
458 | 0 | } |
459 | | |
460 | | std::string operator()(std::string const& s) override |
461 | 0 | { |
462 | 0 | if (this->Selector->InSelection(s)) { |
463 | 0 | return cmStrCat(this->Prepend, s); |
464 | 0 | } |
465 | | |
466 | 0 | return s; |
467 | 0 | } |
468 | | |
469 | | private: |
470 | | std::string Prepend; |
471 | | }; |
472 | | class TransformActionToUpper : public TransformAction |
473 | | { |
474 | | public: |
475 | | std::string operator()(std::string const& s) override |
476 | 0 | { |
477 | 0 | if (this->Selector->InSelection(s)) { |
478 | 0 | return cmSystemTools::UpperCase(s); |
479 | 0 | } |
480 | | |
481 | 0 | return s; |
482 | 0 | } |
483 | | }; |
484 | | class TransformActionToLower : public TransformAction |
485 | | { |
486 | | public: |
487 | | std::string operator()(std::string const& s) override |
488 | 0 | { |
489 | 0 | if (this->Selector->InSelection(s)) { |
490 | 0 | return cmSystemTools::LowerCase(s); |
491 | 0 | } |
492 | | |
493 | 0 | return s; |
494 | 0 | } |
495 | | }; |
496 | | class TransformActionStrip : public TransformAction |
497 | | { |
498 | | public: |
499 | | std::string operator()(std::string const& s) override |
500 | 0 | { |
501 | 0 | if (this->Selector->InSelection(s)) { |
502 | 0 | return cmTrimWhitespace(s); |
503 | 0 | } |
504 | | |
505 | 0 | return s; |
506 | 0 | } |
507 | | }; |
508 | | class TransformActionGenexStrip : public TransformAction |
509 | | { |
510 | | public: |
511 | | std::string operator()(std::string const& s) override |
512 | 0 | { |
513 | 0 | if (this->Selector->InSelection(s)) { |
514 | 0 | return cmGeneratorExpression::Preprocess( |
515 | 0 | s, cmGeneratorExpression::StripAllGeneratorExpressions); |
516 | 0 | } |
517 | | |
518 | 0 | return s; |
519 | 0 | } |
520 | | }; |
521 | | class TransformActionReplace : public TransformAction |
522 | | { |
523 | | public: |
524 | | using TransformAction::Initialize; |
525 | | |
526 | | void Initialize(TransformSelector* selector, std::string const& regex, |
527 | | std::string const& replace) override |
528 | 0 | { |
529 | 0 | TransformAction::Initialize(selector); |
530 | 0 | this->ReplaceHelper = cm::make_unique<cmStringReplaceHelper>( |
531 | 0 | regex, replace, selector->Makefile); |
532 | |
|
533 | 0 | if (!this->ReplaceHelper->IsRegularExpressionValid()) { |
534 | 0 | throw transform_error( |
535 | 0 | cmStrCat("sub-command TRANSFORM, action REPLACE: Failed to compile " |
536 | 0 | "regex \"", |
537 | 0 | regex, "\".")); |
538 | 0 | } |
539 | 0 | if (!this->ReplaceHelper->IsReplaceExpressionValid()) { |
540 | 0 | throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ", |
541 | 0 | this->ReplaceHelper->GetError(), '.')); |
542 | 0 | } |
543 | 0 | } |
544 | | void Initialize(TransformSelector* selector, |
545 | | std::vector<std::string> const& args) override |
546 | 0 | { |
547 | 0 | this->Initialize(selector, args[0], args[1]); |
548 | 0 | } |
549 | | |
550 | | std::string operator()(std::string const& s) override |
551 | 0 | { |
552 | 0 | if (this->Selector->InSelection(s)) { |
553 | | // Scan through the input for all matches. |
554 | 0 | std::string output; |
555 | |
|
556 | 0 | if (!this->ReplaceHelper->Replace(s, output)) { |
557 | 0 | throw transform_error( |
558 | 0 | cmStrCat("sub-command TRANSFORM, action REPLACE: ", |
559 | 0 | this->ReplaceHelper->GetError(), '.')); |
560 | 0 | } |
561 | | |
562 | 0 | return output; |
563 | 0 | } |
564 | | |
565 | 0 | return s; |
566 | 0 | } |
567 | | |
568 | | private: |
569 | | std::unique_ptr<cmStringReplaceHelper> ReplaceHelper; |
570 | | }; |
571 | | |
572 | | class TransformActionApply : public TransformAction |
573 | | { |
574 | | public: |
575 | | using TransformAction::Initialize; |
576 | | |
577 | | void Initialize(TransformSelector* selector, std::string const& functionName, |
578 | | cmMakefile& makefile) |
579 | 0 | { |
580 | 0 | TransformAction::Initialize(selector); |
581 | 0 | this->FunctionName = functionName; |
582 | 0 | this->Makefile = &makefile; |
583 | | |
584 | | // Validate: command must exist |
585 | 0 | if (!makefile.GetState()->GetCommand(this->FunctionName)) { |
586 | 0 | throw transform_error( |
587 | 0 | cmStrCat("sub-command TRANSFORM, action APPLY: unknown function \"", |
588 | 0 | this->FunctionName, "\".")); |
589 | 0 | } |
590 | 0 | } |
591 | | |
592 | | void Initialize(TransformSelector* /*selector*/, |
593 | | std::vector<std::string> const& /*args*/) override |
594 | 0 | { |
595 | | // This overload must not be used for APPLY — it lacks cmMakefile context. |
596 | 0 | throw transform_error( |
597 | 0 | "sub-command TRANSFORM, action APPLY requires cmMakefile context."); |
598 | 0 | } |
599 | | |
600 | | std::string operator()(std::string const& s) override |
601 | 0 | { |
602 | 0 | if (!this->Selector->InSelection(s)) { |
603 | 0 | return s; |
604 | 0 | } |
605 | | |
606 | | // Use a unique output variable name to avoid collisions |
607 | 0 | std::string const outputVar = "_list_transform_apply_out_"; |
608 | | |
609 | | // Unset the output variable before calling |
610 | 0 | this->Makefile->RemoveDefinition(outputVar); |
611 | | |
612 | | // Build the function call: functionName(s, outputVar) |
613 | 0 | cmListFileContext context = this->Makefile->GetBacktrace().Top(); |
614 | 0 | std::vector<cmListFileArgument> funcArgs; |
615 | 0 | funcArgs.emplace_back(s, cmListFileArgument::Quoted, context.Line); |
616 | 0 | funcArgs.emplace_back(outputVar, cmListFileArgument::Quoted, context.Line); |
617 | 0 | cmListFileFunction func{ this->FunctionName, context.Line, context.Line, |
618 | 0 | std::move(funcArgs) }; |
619 | |
|
620 | 0 | cmExecutionStatus status(*this->Makefile); |
621 | 0 | if (!this->Makefile->ExecuteCommand(func, status) || |
622 | 0 | status.GetNestedError()) { |
623 | 0 | throw transform_error( |
624 | 0 | cmStrCat("sub-command TRANSFORM, action APPLY: function \"", |
625 | 0 | this->FunctionName, "\" failed during execution.")); |
626 | 0 | } |
627 | | |
628 | | // Read back the output variable |
629 | 0 | cmValue result = this->Makefile->GetDefinition(outputVar); |
630 | 0 | if (!result) { |
631 | 0 | throw transform_error( |
632 | 0 | cmStrCat("sub-command TRANSFORM, action APPLY: function \"", |
633 | 0 | this->FunctionName, "\" did not set the output variable.")); |
634 | 0 | } |
635 | | |
636 | | // Copy the result before cleaning up (RemoveDefinition invalidates the |
637 | | // cmValue pointer). |
638 | 0 | std::string output = *result; |
639 | | |
640 | | // Clean up |
641 | 0 | this->Makefile->RemoveDefinition(outputVar); |
642 | |
|
643 | 0 | return output; |
644 | 0 | } |
645 | | |
646 | | private: |
647 | | std::string FunctionName; |
648 | | cmMakefile* Makefile = nullptr; |
649 | | }; |
650 | | |
651 | | // Descriptor of action |
652 | | // Arity: number of arguments required for the action |
653 | | // Transform: Object implementing the action |
654 | | struct ActionDescriptor |
655 | | { |
656 | | ActionDescriptor(cmList::TransformAction action) |
657 | 0 | : Action(action) |
658 | 0 | { |
659 | 0 | } |
660 | | ActionDescriptor(cmList::TransformAction action, std::string name, |
661 | | std::size_t arity, |
662 | | std::unique_ptr<TransformAction> transform) |
663 | 0 | : Action(action) |
664 | 0 | , Name(std::move(name)) |
665 | 0 | , Arity(arity) |
666 | 0 | , Transform(std::move(transform)) |
667 | 0 | { |
668 | 0 | } |
669 | | |
670 | 0 | operator cmList::TransformAction() const { return this->Action; } |
671 | | |
672 | | cmList::TransformAction Action; |
673 | | std::string Name; |
674 | | std::size_t Arity = 0; |
675 | | std::unique_ptr<TransformAction> Transform; |
676 | | }; |
677 | | |
678 | | // Build a set of supported actions. |
679 | | using ActionDescriptorSet = std::set< |
680 | | ActionDescriptor, |
681 | | std::function<bool(cmList::TransformAction, cmList::TransformAction)>>; |
682 | | |
683 | | ActionDescriptorSet Descriptors([](cmList::TransformAction x, |
684 | 0 | cmList::TransformAction y) { |
685 | 0 | return x < y; |
686 | 0 | }); |
687 | | |
688 | | ActionDescriptorSet::iterator TransformConfigure( |
689 | | cmList::TransformAction action, |
690 | | std::unique_ptr<cmList::TransformSelector>& selector, std::size_t arity) |
691 | 0 | { |
692 | 0 | if (Descriptors.empty()) { |
693 | 0 | Descriptors.emplace(cmList::TransformAction::APPEND, "APPEND", 1, |
694 | 0 | cm::make_unique<TransformActionAppend>()); |
695 | 0 | Descriptors.emplace(cmList::TransformAction::PREPEND, "PREPEND", 1, |
696 | 0 | cm::make_unique<TransformActionPrepend>()); |
697 | 0 | Descriptors.emplace(cmList::TransformAction::TOUPPER, "TOUPPER", 0, |
698 | 0 | cm::make_unique<TransformActionToUpper>()); |
699 | 0 | Descriptors.emplace(cmList::TransformAction::TOLOWER, "TOLOWER", 0, |
700 | 0 | cm::make_unique<TransformActionToLower>()); |
701 | 0 | Descriptors.emplace(cmList::TransformAction::STRIP, "STRIP", 0, |
702 | 0 | cm::make_unique<TransformActionStrip>()); |
703 | 0 | Descriptors.emplace(cmList::TransformAction::GENEX_STRIP, "GENEX_STRIP", 0, |
704 | 0 | cm::make_unique<TransformActionGenexStrip>()); |
705 | 0 | Descriptors.emplace(cmList::TransformAction::REPLACE, "REPLACE", 2, |
706 | 0 | cm::make_unique<TransformActionReplace>()); |
707 | 0 | Descriptors.emplace(cmList::TransformAction::APPLY, "APPLY", 1, |
708 | 0 | cm::make_unique<TransformActionApply>()); |
709 | 0 | } |
710 | |
|
711 | 0 | auto descriptor = Descriptors.find(action); |
712 | 0 | if (descriptor == Descriptors.end()) { |
713 | 0 | throw transform_error(cmStrCat(" sub-command TRANSFORM, ", |
714 | 0 | static_cast<int>(action), |
715 | 0 | " invalid action.")); |
716 | 0 | } |
717 | | |
718 | 0 | if (descriptor->Arity != arity) { |
719 | 0 | throw transform_error(cmStrCat("sub-command TRANSFORM, action ", |
720 | 0 | descriptor->Name, " expects ", |
721 | 0 | descriptor->Arity, " argument(s).")); |
722 | 0 | } |
723 | 0 | if (!selector) { |
724 | 0 | selector = cm::make_unique<TransformNoSelector>(); |
725 | 0 | } |
726 | |
|
727 | 0 | return descriptor; |
728 | 0 | } |
729 | | } |
730 | | |
731 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::New() |
732 | 0 | { |
733 | 0 | return cm::make_unique<TransformNoSelector>(); |
734 | 0 | } |
735 | | |
736 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT( |
737 | | std::initializer_list<index_type> indexes) |
738 | 0 | { |
739 | 0 | return cm::make_unique<TransformSelectorAt>( |
740 | 0 | std::vector<index_type>{ indexes.begin(), indexes.end() }); |
741 | 0 | ; |
742 | 0 | } |
743 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT( |
744 | | std::vector<index_type> const& indexes) |
745 | 0 | { |
746 | 0 | return cm::make_unique<TransformSelectorAt>(indexes); |
747 | 0 | } |
748 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT( |
749 | | std::vector<index_type>&& indexes) |
750 | 0 | { |
751 | 0 | return cm::make_unique<TransformSelectorAt>(std::move(indexes)); |
752 | 0 | } |
753 | | |
754 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR( |
755 | | std::initializer_list<index_type> indexes) |
756 | 0 | { |
757 | 0 | if (indexes.size() < 2 || indexes.size() > 3) { |
758 | 0 | throw transform_error("sub-command TRANSFORM, selector FOR " |
759 | 0 | "expects 2 or 3 arguments"); |
760 | 0 | } |
761 | 0 | if (indexes.size() == 3 && *(indexes.begin() + 2) < 0) { |
762 | 0 | throw transform_error("sub-command TRANSFORM, selector FOR expects " |
763 | 0 | "positive numeric value for <step>."); |
764 | 0 | } |
765 | | |
766 | 0 | return cm::make_unique<TransformSelectorFor>( |
767 | 0 | *indexes.begin(), *(indexes.begin() + 1), |
768 | 0 | indexes.size() == 3 ? *(indexes.begin() + 2) : 1); |
769 | 0 | } |
770 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR( |
771 | | std::vector<index_type> const& indexes) |
772 | 0 | { |
773 | 0 | if (indexes.size() < 2 || indexes.size() > 3) { |
774 | 0 | throw transform_error("sub-command TRANSFORM, selector FOR " |
775 | 0 | "expects 2 or 3 arguments"); |
776 | 0 | } |
777 | 0 | if (indexes.size() == 3 && indexes[2] < 0) { |
778 | 0 | throw transform_error("sub-command TRANSFORM, selector FOR expects " |
779 | 0 | "positive numeric value for <step>."); |
780 | 0 | } |
781 | | |
782 | 0 | return cm::make_unique<TransformSelectorFor>( |
783 | 0 | indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1); |
784 | 0 | } |
785 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR( |
786 | | std::vector<index_type>&& indexes) |
787 | 0 | { |
788 | 0 | if (indexes.size() < 2 || indexes.size() > 3) { |
789 | 0 | throw transform_error("sub-command TRANSFORM, selector FOR " |
790 | 0 | "expects 2 or 3 arguments"); |
791 | 0 | } |
792 | 0 | if (indexes.size() == 3 && indexes[2] < 0) { |
793 | 0 | throw transform_error("sub-command TRANSFORM, selector FOR expects " |
794 | 0 | "positive numeric value for <step>."); |
795 | 0 | } |
796 | | |
797 | 0 | return cm::make_unique<TransformSelectorFor>( |
798 | 0 | indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1); |
799 | 0 | } |
800 | | |
801 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX( |
802 | | std::string const& regex) |
803 | 0 | { |
804 | 0 | std::unique_ptr<::TransformSelector> selector = |
805 | 0 | cm::make_unique<TransformSelectorRegex>(regex); |
806 | 0 | if (!selector->Validate()) { |
807 | 0 | throw transform_error( |
808 | 0 | cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile " |
809 | 0 | "regex \"", |
810 | 0 | regex, "\".")); |
811 | 0 | } |
812 | | // weird construct to please all compilers |
813 | 0 | return std::unique_ptr<cmList::TransformSelector>(selector.release()); |
814 | 0 | } |
815 | | std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX( |
816 | | std::string&& regex) |
817 | 0 | { |
818 | 0 | std::unique_ptr<::TransformSelector> selector = |
819 | 0 | cm::make_unique<TransformSelectorRegex>(std::move(regex)); |
820 | 0 | if (!selector->Validate()) { |
821 | 0 | throw transform_error( |
822 | 0 | cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile " |
823 | 0 | "regex \"", |
824 | 0 | regex, "\".")); |
825 | 0 | } |
826 | | // weird construct to please all compilers |
827 | 0 | return std::unique_ptr<cmList::TransformSelector>(selector.release()); |
828 | 0 | } |
829 | | |
830 | | cmList& cmList::transform(TransformAction action, |
831 | | std::unique_ptr<TransformSelector> selector) |
832 | 0 | { |
833 | 0 | auto descriptor = TransformConfigure(action, selector, 0); |
834 | |
|
835 | 0 | descriptor->Transform->Initialize( |
836 | 0 | static_cast<::TransformSelector*>(selector.get())); |
837 | |
|
838 | 0 | static_cast<::TransformSelector&>(*selector).Transform( |
839 | 0 | this->Values, [&descriptor](std::string const& s) -> std::string { |
840 | 0 | return (*descriptor->Transform)(s); |
841 | 0 | }); |
842 | |
|
843 | 0 | return *this; |
844 | 0 | } |
845 | | |
846 | | cmList& cmList::transform(TransformAction action, std::string const& arg, |
847 | | std::unique_ptr<TransformSelector> selector) |
848 | 0 | { |
849 | 0 | auto descriptor = TransformConfigure(action, selector, 1); |
850 | |
|
851 | 0 | descriptor->Transform->Initialize( |
852 | 0 | static_cast<::TransformSelector*>(selector.get()), arg); |
853 | |
|
854 | 0 | static_cast<::TransformSelector&>(*selector).Transform( |
855 | 0 | this->Values, [&descriptor](std::string const& s) -> std::string { |
856 | 0 | return (*descriptor->Transform)(s); |
857 | 0 | }); |
858 | |
|
859 | 0 | return *this; |
860 | 0 | } |
861 | | |
862 | | cmList& cmList::transform(TransformAction action, std::string const& arg1, |
863 | | std::string const& arg2, |
864 | | std::unique_ptr<TransformSelector> selector) |
865 | 0 | { |
866 | 0 | auto descriptor = TransformConfigure(action, selector, 2); |
867 | |
|
868 | 0 | descriptor->Transform->Initialize( |
869 | 0 | static_cast<::TransformSelector*>(selector.get()), arg1, arg2); |
870 | |
|
871 | 0 | static_cast<::TransformSelector&>(*selector).Transform( |
872 | 0 | this->Values, [&descriptor](std::string const& s) -> std::string { |
873 | 0 | return (*descriptor->Transform)(s); |
874 | 0 | }); |
875 | |
|
876 | 0 | return *this; |
877 | 0 | } |
878 | | |
879 | | cmList& cmList::transform(TransformAction action, |
880 | | std::vector<std::string> const& args, |
881 | | std::unique_ptr<TransformSelector> selector) |
882 | 0 | { |
883 | 0 | auto descriptor = TransformConfigure(action, selector, args.size()); |
884 | |
|
885 | 0 | descriptor->Transform->Initialize( |
886 | 0 | static_cast<::TransformSelector*>(selector.get()), args); |
887 | |
|
888 | 0 | static_cast<::TransformSelector&>(*selector).Transform( |
889 | 0 | this->Values, [&descriptor](std::string const& s) -> std::string { |
890 | 0 | return (*descriptor->Transform)(s); |
891 | 0 | }); |
892 | |
|
893 | 0 | return *this; |
894 | 0 | } |
895 | | |
896 | | cmList& cmList::transform(TransformAction action, std::string const& arg, |
897 | | cmMakefile& makefile, |
898 | | std::unique_ptr<TransformSelector> selector) |
899 | 0 | { |
900 | 0 | auto descriptor = TransformConfigure(action, selector, 1); |
901 | |
|
902 | 0 | auto* applyAction = |
903 | 0 | static_cast<TransformActionApply*>(descriptor->Transform.get()); |
904 | 0 | applyAction->Initialize(static_cast<::TransformSelector*>(selector.get()), |
905 | 0 | arg, makefile); |
906 | |
|
907 | 0 | static_cast<::TransformSelector&>(*selector).Transform( |
908 | 0 | this->Values, [&descriptor](std::string const& s) -> std::string { |
909 | 0 | return (*descriptor->Transform)(s); |
910 | 0 | }); |
911 | |
|
912 | 0 | return *this; |
913 | 0 | } |
914 | | |
915 | | std::string& cmList::append(std::string& list, std::string&& value) |
916 | 0 | { |
917 | 0 | if (list.empty()) { |
918 | 0 | list = std::move(value); |
919 | 0 | } else { |
920 | 0 | list += cmStrCat(cmList::element_separator, value); |
921 | 0 | } |
922 | |
|
923 | 0 | return list; |
924 | 0 | } |
925 | | std::string& cmList::append(std::string& list, cm::string_view value) |
926 | 0 | { |
927 | 0 | return cmList::append(list, std::string{ value }); |
928 | 0 | } |
929 | | |
930 | | std::string& cmList::prepend(std::string& list, std::string&& value) |
931 | 0 | { |
932 | 0 | if (list.empty()) { |
933 | 0 | list = std::move(value); |
934 | 0 | } else { |
935 | 0 | list.insert(0, cmStrCat(value, cmList::element_separator)); |
936 | 0 | } |
937 | |
|
938 | 0 | return list; |
939 | 0 | } |
940 | | std::string& cmList::prepend(std::string& list, cm::string_view value) |
941 | 0 | { |
942 | 0 | return cmList::prepend(list, std::string{ value }); |
943 | 0 | } |
944 | | |
945 | | cmList::size_type cmList::ComputeIndex(index_type pos, bool boundCheck) const |
946 | 0 | { |
947 | 0 | if (boundCheck) { |
948 | 0 | if (this->Values.empty()) { |
949 | 0 | throw std::out_of_range( |
950 | 0 | cmStrCat("index: ", pos, " out of range (0, 0)")); |
951 | 0 | } |
952 | | |
953 | 0 | auto index = pos; |
954 | 0 | if (!this->Values.empty()) { |
955 | 0 | auto length = this->Values.size(); |
956 | 0 | if (index < 0) { |
957 | 0 | index = static_cast<index_type>(length) + index; |
958 | 0 | } |
959 | 0 | if (index < 0 || length <= static_cast<size_type>(index)) { |
960 | 0 | throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-", |
961 | 0 | this->Values.size(), ", ", |
962 | 0 | this->Values.size() - 1, ')')); |
963 | 0 | } |
964 | 0 | } |
965 | 0 | return index; |
966 | 0 | } |
967 | | |
968 | 0 | return pos < 0 ? this->Values.size() + pos : pos; |
969 | 0 | } |
970 | | cmList::size_type cmList::ComputeInsertIndex(index_type pos, |
971 | | bool boundCheck) const |
972 | 0 | { |
973 | 0 | if (boundCheck) { |
974 | 0 | if (this->Values.empty() && pos != 0) { |
975 | 0 | throw std::out_of_range( |
976 | 0 | cmStrCat("index: ", pos, " out of range (0, 0)")); |
977 | 0 | } |
978 | | |
979 | 0 | auto index = pos; |
980 | 0 | if (!this->Values.empty()) { |
981 | 0 | auto length = this->Values.size(); |
982 | 0 | if (index < 0) { |
983 | 0 | index = static_cast<index_type>(length) + index; |
984 | 0 | } |
985 | 0 | if (index < 0 || length < static_cast<size_type>(index)) { |
986 | 0 | throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-", |
987 | 0 | this->Values.size(), ", ", |
988 | 0 | this->Values.size(), ')')); |
989 | 0 | } |
990 | 0 | } |
991 | 0 | return index; |
992 | 0 | } |
993 | | |
994 | 0 | return pos < 0 ? this->Values.size() + pos : pos; |
995 | 0 | } |
996 | | |
997 | | cmList cmList::GetItems(std::vector<index_type>&& indexes) const |
998 | 0 | { |
999 | 0 | cmList listItems; |
1000 | |
|
1001 | 0 | for (auto index : indexes) { |
1002 | 0 | listItems.emplace_back(this->get_item(index)); |
1003 | 0 | } |
1004 | |
|
1005 | 0 | return listItems; |
1006 | 0 | } |
1007 | | |
1008 | | cmList& cmList::RemoveItems(std::vector<index_type>&& indexes) |
1009 | 0 | { |
1010 | 0 | if (indexes.empty()) { |
1011 | 0 | return *this; |
1012 | 0 | } |
1013 | | |
1014 | | // compute all indexes |
1015 | 0 | std::vector<size_type> idx(indexes.size()); |
1016 | 0 | std::transform(indexes.cbegin(), indexes.cend(), idx.begin(), |
1017 | 0 | [this](index_type index) -> size_type { |
1018 | 0 | return this->ComputeIndex(index); |
1019 | 0 | }); |
1020 | |
|
1021 | 0 | std::sort(idx.begin(), idx.end(), |
1022 | 0 | [](size_type l, size_type r) { return l > r; }); |
1023 | 0 | auto newEnd = std::unique(idx.begin(), idx.end()); |
1024 | 0 | idx.erase(newEnd, idx.end()); |
1025 | |
|
1026 | 0 | for (auto index : idx) { |
1027 | 0 | this->erase(this->begin() + index); |
1028 | 0 | } |
1029 | |
|
1030 | 0 | return *this; |
1031 | 0 | } |
1032 | | |
1033 | | cmList& cmList::RemoveItems(std::vector<std::string>&& items) |
1034 | 0 | { |
1035 | 0 | std::sort(items.begin(), items.end()); |
1036 | 0 | auto last = std::unique(items.begin(), items.end()); |
1037 | 0 | auto first = items.begin(); |
1038 | |
|
1039 | 0 | auto newEnd = cmRemoveMatching(this->Values, cmMakeRange(first, last)); |
1040 | 0 | this->Values.erase(newEnd, this->Values.end()); |
1041 | |
|
1042 | 0 | return *this; |
1043 | 0 | } |
1044 | | |
1045 | | cmList::container_type::iterator cmList::Insert( |
1046 | | container_type& container, container_type::const_iterator pos, |
1047 | | std::string&& value, ExpandElements expandElements, |
1048 | | EmptyElements emptyElements) |
1049 | 14.2k | { |
1050 | 14.2k | auto delta = std::distance(container.cbegin(), pos); |
1051 | 14.2k | auto insertPos = container.begin() + delta; |
1052 | | |
1053 | 14.2k | if (expandElements == ExpandElements::Yes) { |
1054 | | // If argument is empty, it is an empty list. |
1055 | 14.2k | if (emptyElements == EmptyElements::No && value.empty()) { |
1056 | 0 | return insertPos; |
1057 | 0 | } |
1058 | | |
1059 | | // if there are no ; in the name then just copy the current string |
1060 | 14.2k | if (value.find(';') == std::string::npos) { |
1061 | 9.91k | return container.insert(insertPos, std::move(value)); |
1062 | 9.91k | } |
1063 | | |
1064 | 4.32k | std::string newValue; |
1065 | | // Break the string at non-escaped semicolons not nested in []. |
1066 | 4.32k | int squareNesting = 0; |
1067 | 4.32k | auto last = value.begin(); |
1068 | 4.32k | auto const cend = value.end(); |
1069 | 664k | for (auto c = last; c != cend; ++c) { |
1070 | 660k | switch (*c) { |
1071 | 20.2k | case '\\': { |
1072 | | // We only want to allow escaping of semicolons. Other |
1073 | | // escapes should not be processed here. |
1074 | 20.2k | auto cnext = c + 1; |
1075 | 20.2k | if ((cnext != cend) && *cnext == ';') { |
1076 | 9.06k | newValue.append(last, c); |
1077 | | // Skip over the escape character |
1078 | 9.06k | last = cnext; |
1079 | 9.06k | c = cnext; |
1080 | 9.06k | } |
1081 | 20.2k | } break; |
1082 | 17.7k | case '[': { |
1083 | 17.7k | ++squareNesting; |
1084 | 17.7k | } break; |
1085 | 51.5k | case ']': { |
1086 | 51.5k | --squareNesting; |
1087 | 51.5k | } break; |
1088 | 41.5k | case ';': { |
1089 | | // brackets. |
1090 | 41.5k | if (squareNesting == 0) { |
1091 | 32.0k | newValue.append(last, c); |
1092 | | // Skip over the semicolon |
1093 | 32.0k | last = c + 1; |
1094 | 32.0k | if (!newValue.empty() || emptyElements == EmptyElements::Yes) { |
1095 | | // Add the last argument. |
1096 | 25.8k | insertPos = container.insert(insertPos, newValue); |
1097 | 25.8k | insertPos++; |
1098 | 25.8k | newValue.clear(); |
1099 | 25.8k | } |
1100 | 32.0k | } |
1101 | 41.5k | } break; |
1102 | 529k | default: { |
1103 | | // Just append this character. |
1104 | 529k | } break; |
1105 | 660k | } |
1106 | 660k | } |
1107 | 4.32k | newValue.append(last, cend); |
1108 | 4.32k | if (!newValue.empty() || emptyElements == EmptyElements::Yes) { |
1109 | | // Add the last argument. |
1110 | 2.62k | container.insert(insertPos, std::move(newValue)); |
1111 | 2.62k | } |
1112 | 4.32k | } else if (!value.empty() || emptyElements == EmptyElements::Yes) { |
1113 | 0 | return container.insert(insertPos, std::move(value)); |
1114 | 0 | } |
1115 | 4.32k | return container.begin() + delta; |
1116 | 14.2k | } |
1117 | | |
1118 | | std::string const& cmList::ToString(BT<std::string> const& s) |
1119 | 0 | { |
1120 | 0 | return s.Value; |
1121 | 0 | } |