45 halp_meta(name,
"Regex")
46 halp_meta(c_name,
"avnd_regex")
47 halp_meta(category,
"Control/Mappings")
48 halp_meta(author,
"ossia score")
51 "Match, search, replace or split text with a regular expression (RE2 syntax). "
52 "Each capture group gets an outlet, named after the group for (?P<name>...). "
53 "Numbers and lists are matched as their text.")
54 halp_meta(uuid,
"ef63f9b2-cc68-4b25-825e-e3b126875086")
78 halp_meta(name,
"Input")
79 halp_meta(description,
"The text to process. Numbers and lists are their text; "
80 "an impulse processes the last input again.")
81 ossia::value_port* value{};
82 static constexpr bool is_event() {
return true; }
85 struct : halp::lineedit<
"Pattern",
"">
87 halp_meta(description,
"RE2 syntax; (?P<name>...) names a group and its outlet")
88 void update(
Regex& self) { self.compile(); }
89 static std::function<void(
Regex&,
const std::string&)> on_controller_interaction()
91 return [](
Regex& self,
const std::string& pattern) {
97 struct : halp::combobox_t<
"Mode",
Mode>
99 void update(
Regex& self) { self.compile(); }
102 struct : halp::lineedit<
"Replacement",
"">
104 halp_meta(description,
"Replace mode: \\0 is the match, \\1 to \\9 the groups "
105 "($1 and %1 work too)")
106 void update(
Regex& self) { self.compile(); }
109 struct : halp::toggle<
"Global", halp::default_on_toggle>
111 halp_meta(description,
"Replace mode: every match, or only the first one")
114 struct : halp::toggle<
"Ignore case">
116 void update(
Regex& self) { self.compile(); }
119 struct : halp::toggle<
"Multiline">
121 halp_meta(description,
"^ and $ match at every line")
122 void update(
Regex& self) { self.compile(); }
125 struct : halp::toggle<
"Numbers">
127 halp_meta(description,
"Groups that are numbers are sent as numbers")
133 halp_meta(name,
"Group {}")
134 ossia::value_port* value{};
135 static constexpr bool is_event() {
return true; }
142 halp_meta(name,
"Match")
144 "the text replaced;
Split: the list of pieces")
145 ossia::value_port* value{};
146 static constexpr bool is_event() {
return true; }
150 halp_meta(name,
"Groups")
151 halp_meta(description, "The groups of the match, as a list;
Search all: a list "
153 ossia::value_port* value{};
154 static constexpr bool is_event() {
return true; }
158 halp_meta(name,
"Matched")
159 halp_meta(description, "true or false, for every input")
160 ossia::value_port* value{};
161 static constexpr bool is_event() {
return true; }
165 halp_meta(name,
"Unmatched")
166 halp_meta(description, "The input, when it does not match")
167 ossia::value_port* value{};
168 static constexpr bool is_event() {
return true; }
172 halp_meta(name,
"Error")
173 halp_meta(description, "Why the pattern or the replacement is not valid; an "
174 "empty
string once they are")
175 ossia::value_port* value{};
176 static constexpr bool is_event() {
return true; }
178 halp::keyed_dynamic_port<capture_port> captures;
181 using tick = ossia::token_request;
182 ossia::exec_state_facade ossia_state;
191 std::vector<std::string> groups;
193 opts.set_log_errors(
false);
194 RE2 re{pattern, opts};
197 const auto& names = re.CapturingGroupNames();
198 for(
int i = 1; i <= re.NumberOfCapturingGroups(); i++)
200 auto it = names.find(i);
201 groups.push_back(it != names.end() ? it->second : std::string{});
206 bool in_class =
false;
207 for(std::size_t i = 0; i < pattern.size(); i++)
209 const char c = pattern[i];
227 if(i + 1 < pattern.size() && pattern[i + 1] ==
'?')
230 std::size_t n = i + 2;
231 if(n < pattern.size() && pattern[n] ==
'P')
233 if(n < pattern.size() && pattern[n] ==
'<')
235 const auto close = pattern.find(
'>', n + 1);
237 pattern.substr(n + 1, close == std::string::npos ? std::string::npos
242 groups.emplace_back();
249 static halp::string_list_value
captureRows(
const std::string& pattern)
253 halp::string_list_value rows;
255 std::vector<int> used;
256 for(
int i = 1; i <= count; i++)
259 const std::string& name = groups[i - 1];
263 uint32_t h = 2166136261u;
264 for(
unsigned char c : name)
265 h = (h ^ c) * 16777619u;
266 key = 20000 + int(h % 1'000'000u);
267 while(std::find(used.begin(), used.end(), key) != used.end())
275 rows.emplace_back(key, name);
284 out.reserve(s.size());
285 for(std::size_t i = 0; i < s.size(); i++)
288 if((c ==
'$' || c ==
'%') && i + 1 < s.size())
290 const char n = s[i + 1];
291 if(n >=
'0' && n <=
'9')
314 std::string replacement;
323 std::unique_ptr<RE2> re;
326 std::vector<re2::StringPiece> submatches;
333 opts.set_log_errors(
false);
334 opts.set_case_sensitive(!spec.ignore_case);
335 auto re = std::make_unique<RE2>(
336 spec.multiline ?
"(?m)" + spec.pattern : spec.pattern, opts);
339 c.error = re->error();
346 if(std::string why; !re->CheckRewriteString(rw, &why))
348 c.error = std::move(why);
352 c.submatches.assign(1 + re->NumberOfCapturingGroups(), {});
353 c.re = std::move(re);
354 c.rewrite = std::move(rw);
360 std::function<void(
Spec, uint64_t)> request;
361 static std::function<void(
Regex&)> work(
Spec spec, uint64_t generation)
363 auto c = std::make_shared<Compiled>(build(spec));
364 return [c = std::move(c), generation](
Regex& self)
mutable {
366 if(generation == self.generation)
367 self.apply(std::move(*c));
372 std::unique_ptr<RE2> re;
374 std::string error_text;
378 std::vector<re2::StringPiece> submatches;
379 std::string last_text;
381 uint64_t generation{};
383 void setError(std::string message)
385 const bool failing = !message.empty();
386 if(failing || had_error)
388 error_text = std::move(message);
394 void apply(Compiled&& c)
396 re = std::move(c.re);
397 rewrite = std::move(c.rewrite);
398 submatches = std::move(c.submatches);
399 setError(std::move(c.error));
407 inputs.pattern.value, inputs.replacement.value, inputs.mode.value,
408 inputs.ignore_case.value, inputs.multiline.value};
410 const auto thread = ossia::get_current_thread_type();
412 && (thread == ossia::thread_type::Audio || thread == ossia::thread_type::AudioTask))
413 worker.request(std::move(spec), generation);
420 static void appendText(std::string& out,
const ossia::value& v)
424 case ossia::val_type::STRING:
425 out += *v.target<std::string>();
427 case ossia::val_type::INT:
428 out += std::to_string(*v.target<
int>());
430 case ossia::val_type::FLOAT: {
431 fmt::format_to(std::back_inserter(out),
"{}", *v.target<
float>());
434 case ossia::val_type::BOOL:
435 out += *v.target<
bool>() ?
"true" :
"false";
437 case ossia::val_type::LIST: {
439 for(
auto& e : *v.target<std::vector<ossia::value>>())
448 case ossia::val_type::VEC2F:
449 case ossia::val_type::VEC3F:
450 case ossia::val_type::VEC4F:
451 appendText(out, ossia::convert<std::vector<ossia::value>>(v));
461 std::string_view text{s.data(), s.size()};
462 if(inputs.numbers.value && !text.empty())
465 if(
auto [p, ec] = std::from_chars(text.data(), text.data() + text.size(), i);
466 ec == std::errc{} && p == text.data() + text.size())
469 if(avnd_tools::value_serialization::parse_float64(text, d) && std::isfinite(d)
470 && std::abs(d) <= std::numeric_limits<float>::max())
473 return std::string{text};
476 static void write(ossia::value_port* port, ossia::value v, int64_t ts)
479 port->write_value(std::move(v), ts);
487 std::vector<ossia::value> groups;
488 groups.reserve(submatches.size() - 1);
489 for(std::size_t g = 1; g < submatches.size(); g++)
491 const auto& s = submatches[g];
494 groups.emplace_back(std::string{});
498 if(g - 1 < outputs.captures.ports.size())
499 write(outputs.captures.ports[g - 1].value, v, ts);
500 groups.push_back(std::move(v));
505 void process(
const ossia::value& input, int64_t ts)
508 if(input.target<ossia::impulse>())
519 const re2::StringPiece piece{text};
520 const int n = submatches.size();
523 switch(inputs.mode.value)
527 const auto anchor = inputs.mode.value ==
Match ? RE2::ANCHOR_BOTH : RE2::UNANCHORED;
528 found = re->Match(piece, 0, piece.size(), anchor, submatches.data(), n);
531 write(outputs.match.value, std::string{submatches[0]}, ts);
532 write(outputs.groups.value,
sendGroups(ts), ts);
538 std::vector<ossia::value>
matches, groups, pieces;
539 std::size_t pos = 0, last = 0;
540 while(pos <= piece.size()
541 && re->Match(piece, pos, piece.size(), RE2::UNANCHORED, submatches.data(), n))
544 const auto& m = submatches[0];
545 const std::size_t begin = m.data() - piece.data();
546 const std::size_t end = begin + m.size();
547 if(inputs.mode.value ==
Split)
549 pieces.emplace_back(std::string{text.substr(last, begin - last)});
553 matches.emplace_back(std::string{m});
558 pos = end > begin ? end : end + 1;
562 if(inputs.mode.value ==
Split)
564 pieces.emplace_back(std::string{text.substr(std::min(last, text.size()))});
565 write(outputs.match.value, std::move(pieces), ts);
569 write(outputs.match.value, std::move(
matches), ts);
570 write(outputs.groups.value, std::move(groups), ts);
575 std::string result = text;
576 found = inputs.global.value ? RE2::GlobalReplace(&result, *re, rewrite) > 0
577 : RE2::Replace(&result, *re, rewrite);
579 write(outputs.match.value, std::move(result), ts);
584 write(outputs.matched.value, found, ts);
586 write(outputs.unmatched.value, input, ts);
589 void operator()(
const tick& t)
591 const auto [start, frames] = ossia_state.timings(t);
594 write(outputs.error.value, error_text, start);
597 if(!inputs.input.value || !re)
599 for(
const auto& event : inputs.input.value->get_data())
601 if(event.timestamp < start || event.timestamp >= start + frames)
603 process(event.value, event.timestamp);