mCRL2
Loading...
Searching...
No Matches
unfold_pattern_matching.h
Go to the documentation of this file.
1// Author(s): Ruud Koolen, Thomas Neele
2// Copyright: see the accompanying file COPYING or copy at
3// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
4//
5// Distributed under the Boost Software License, Version 1.0.
6// (See accompanying file LICENSE_1_0.txt or copy at
7// http://www.boost.org/LICENSE_1_0.txt)
8
9#ifndef MCRL2_DATA_UNFOLD_PATTERN_MATCHING_H
10#define MCRL2_DATA_UNFOLD_PATTERN_MATCHING_H
11
12#include "mcrl2/data/join.h"
13#include "mcrl2/data/replace.h"
17
18namespace mcrl2::data
19{
20
21namespace detail
22{
53struct rule
54{
55 std::map<data_expression, data_expression> match_criteria;
59
60 rule(const std::map<data_expression, data_expression>& mc,
61 const data_expression& r,
62 const data_expression& c,
63 const variable_list& v)
64 : match_criteria(mc)
65 , rhs(r)
66 , condition(c)
67 , variables(v)
68 {
69 assert(condition.sort() == sort_bool::bool_());
70 }
71
72};
73
74inline
75std::ostream& operator<<(std::ostream& out, const rule& r)
76{
77 return out << core::detail::print_list(r.variables) << ". " << r.condition << " -> " << core::detail::print_map(r.match_criteria) << " = " << r.rhs;
78}
79
80
84inline
86{
87 if (is_function_symbol(expr))
88 {
89 return atermpp::down_cast<function_symbol>(expr);
90 }
91 if (is_application(expr))
92 {
93 const application& expr_appl = atermpp::down_cast<application>(expr);
94 if (is_function_symbol(expr_appl.head()))
95 {
96 return atermpp::down_cast<function_symbol>(expr_appl.head());
97 }
98 }
99 return function_symbol();
100}
101
107inline
108data_expression construct_condition_rhs(const std::vector<rule>& rules, const data_expression& representative)
109{
110 // data_expression_vector negated_conditions;
111 // for (const rule& r: rules)
112 // {
113 // negated_conditions.push_back(sort_bool::not_(r.condition));
114 // }
115 // data_expression else_clause = join_and(negated_conditions.begin(), negated_conditions.end());
116 // TODO: Check whether else_clause is equivalent to false. Can we use the enumerator for this?
117
118 if (rules.size() == 0)
119 {
120 return representative;
121 }
122
123 data_expression result;
124 for (const rule& r: rules)
125 {
126 std::map<variable, data_expression> substitution_map;
127 for (const auto& [var_expr, pattern]: r.match_criteria)
128 {
129 assert(is_variable(pattern));
130 substitution_map[atermpp::down_cast<variable>(pattern)] = var_expr;
131 }
132 map_substitution<std::map<variable, data_expression> > substitution(substitution_map);
133
134 data_expression condition = replace_variables_capture_avoiding(r.condition, substitution);
135 data_expression rhs = replace_variables_capture_avoiding(r.rhs, substitution);
136
137 if (result == data_expression())
138 {
139 result = rhs;
140 }
141 else
142 {
143 result = lazy::if_(condition, rhs, result);
144 }
145 }
146
147 return result;
148}
149
153template <typename StructInfo>
155 StructInfo& ssf,
157 const std::vector<rule>& rules,
158 const sort_expression& sort
159)
160{
161 if (rules.empty())
162 {
163 return construct_condition_rhs(rules, gen(sort));
164 }
165
166 /*
167 * For each matching rule LHS, check whether pattern matching needs to happen on that LHS.
168 *
169 * We prefer to match on an expression such that *all* rules perform matching on that expression.
170 * For example, for a ruleset representing the following rewrite rules:
171 *
172 * optionally_remove_first_element(false, l) = l
173 * optionally_remove_first_element(true, []) = []
174 * optionally_remove_first_element(true, x |> l) = l
175 *
176 * We prefer to pattern match on the first parameter, not the second. Pattern matching on the
177 * second parameter will (recursively) happen only on the true-branch, not the false-branch.
178 * Pattern matching on the second parameter is still possible if there is no other option, but
179 * it requires splitting the first rewrite rule into different cases for different
180 * constructors -- suboptimal.
181 */
182 data_expression matching_target;
183 enum class MatchType
184 {
185 NONE, INCOMPLETE, PARTIAL, FULL
186 };
187 MatchType matching_type = MatchType::NONE;
188 // iterate over matches, this is the same for all rules, so just use rules[0]
189 // More formally, we have rules[i].match_criteria->first is equivalent for all i.
190 for (const auto& [var_expr, _]: rules[0].match_criteria)
191 {
192 bool variable_seen = false;
193 std::set<function_symbol> constructors_seen;
194 for (const rule& r: rules)
195 {
196 const data_expression& pattern = r.match_criteria.at(var_expr);
197 if (is_variable(pattern))
198 {
199 variable_seen = true;
200 }
201 else
202 {
203 // either function symbol or application of function symbol
204 function_symbol fs = get_top_fs(pattern);
205 // this also means fs != function_symbol()
206 assert(ssf.is_constructor(fs));
207 constructors_seen.insert(fs);
208 }
209 }
210
211 MatchType new_matching_type;
212 if (constructors_seen.empty())
213 {
214 // No pattern matching is possible on this variable.
215 new_matching_type = MatchType::NONE;
216 }
217 else if (variable_seen)
218 {
219 // There are both rules that match on this variable and rules that do not.
220 // That's better than an incomplete matching but worse than a full matching.
221 new_matching_type = MatchType::PARTIAL;
222 }
223 else if (constructors_seen.size() != ssf.get_constructors(var_expr.sort()).size())
224 {
225 // There are constructors for which there are no rules.
226 // Thus, we have an incomplete function definition, that needs to be completed artificially.
227 // A partial matching would be a better choice.
228 new_matching_type = MatchType::INCOMPLETE;
229 }
230 else
231 {
232 // There is a matching rule for each constructor, and no rule with a plain variable.
233 // This variable is a perfect pattern matching candidate.
234 new_matching_type = MatchType::FULL;
235 }
236
237 if (new_matching_type > matching_type)
238 {
239 matching_target = var_expr;
240 matching_type = new_matching_type;
241 }
242 if (matching_type == MatchType::FULL)
243 {
244 break;
245 }
246 }
247
248 if (matching_type == MatchType::NONE)
249 {
250 // No constructor-based matching needs to happen.
251 // All that needs to happen is incorporating the rule conditions.
252 return construct_condition_rhs(rules, gen(sort));
253 }
254
255 /*
256 * For each constructor, find the set of rules that apply to it, rewritten to match the constructor.
257 */
258 // auto type below depends on the type of ssf
259 const auto& match_constructors = ssf.get_constructors(matching_target.sort());
260 std::map<function_symbol, std::vector<rule> > constructor_rules;
261 for (const rule& r: rules)
262 {
263 const data_expression& pattern = r.match_criteria.at(matching_target);
264 if (is_function_symbol(pattern) || is_application(pattern))
265 {
266 /*
267 * For a rule with a constructor pattern, strip the constructor and
268 * introduce patterns for the constructor parameters.
269 */
270 function_symbol constructor = get_top_fs(pattern);
271 assert(constructor != function_symbol());
272 assert(utilities::detail::contains(match_constructors, constructor));
273
274 data_expression_vector parameters;
275 if (is_application(pattern))
276 {
277 const application& pattern_appl = atermpp::down_cast<application>(pattern);
278 parameters.insert(parameters.end(), pattern_appl.begin(), pattern_appl.end());
279 }
280
281 rule rule = r;
282 rule.match_criteria.erase(matching_target);
283 // To prevent creating expressions of the shape head(l) |> tail(l), we perform a substitution here
284 // This is only safe if there are no binders in the right-hand side
285 std::set<data_expression> subexpr = find_data_expressions(rule.rhs);
286 if (!parameters.empty() && std::none_of(subexpr.begin(), subexpr.end(), [](const data_expression& e) { return is_abstraction(e); }))
287 {
288 auto sigma = [&](const data_expression& x) { return x == pattern ? matching_target : x; };
291 }
292 for (std::size_t j = 0; j < parameters.size(); j++)
293 {
294 function_symbol projection_function = ssf.get_projection_funcs(constructor)[j];
295 data_expression lhs_expression = application(projection_function, matching_target);
296 rule.match_criteria[lhs_expression] = parameters[j];
297 }
298 constructor_rules[constructor].push_back(rule);
299 }
300 else
301 {
302 /*
303 * For a rule with a variable pattern that nonetheless needs to pattern match
304 * against the possible constructors for that variable, copy the rule for each
305 * possible constructor. Substitute the original un-matched term for the pattern
306 * variable that disappears.
307 */
308 assert(is_variable(pattern));
309 variable_substitution sigma(atermpp::down_cast<variable>(pattern), matching_target);
312
313 for (const function_symbol& f: match_constructors)
314 {
315 rule rule(r.match_criteria, rhs, condition, r.variables);
316 rule.match_criteria.erase(matching_target);
317
318 set_identifier_generator generator;
319 for (const variable& v: r.variables)
320 {
321 generator.add_identifier(v.name());
322 }
323
324 if (is_function_sort(f.sort()))
325 {
326 function_sort sort(f.sort());
327 std::size_t index = 0;
328 for (const sort_expression& s: sort.domain())
329 {
330 variable variable(generator("v"), s);
331 function_symbol projection_function = ssf.get_projection_funcs(f)[index];
332 data_expression lhs_expression = application(projection_function, matching_target);
333 rule.match_criteria[lhs_expression] = variable;
334 index++;
335 }
336 }
337
338 constructor_rules[f].push_back(rule);
339 }
340 }
341 }
342
343 /*
344 * Construct an rhs of the form if(is_cons1, rhs_cons1, if(is_cons2, rhs_cons2, ...)) or equivalent
345 * The exact form depends on the implementation in ssf
346 */
348 for (const auto& f: match_constructors)
349 {
350 rhs.push_back(construct_rhs(ssf, gen, constructor_rules[f], sort));
351 }
352 return ssf.create_cases(matching_target, rhs);
353}
354
355} // namespace detail
356
362template <typename StructInfo>
363bool is_pattern_matching_rule(StructInfo& ssf, const data_equation& rewrite_rule)
364{
365 // For this rewrite rule to be usable in pattern matching, its lhs must only contain
366 // constructor functions and variables that occur at most once.
367
368 std::set<data_expression> subexpressions = find_data_expressions(rewrite_rule.lhs());
369 subexpressions.erase(rewrite_rule.lhs());
370 if (is_application(rewrite_rule.lhs()))
371 {
372 subexpressions.erase(application(rewrite_rule.lhs()).head());
373 }
374
375 bool all_pattern = std::all_of(subexpressions.begin(), subexpressions.end(), [&ssf](const data_expression& expr) {
376 return
377 is_variable(expr) ||
378 (is_function_symbol(expr) && ssf.is_constructor(atermpp::down_cast<function_symbol>(expr))) ||
379 (is_application(expr) && is_function_symbol(atermpp::down_cast<application>(expr).head()) &&
380 ssf.is_constructor(function_symbol(atermpp::down_cast<application>(expr).head())));
381 });
382 if (!all_pattern)
383 {
384 return false;
385 }
386 if (std::all_of(subexpressions.begin(), subexpressions.end(), [](const data_expression& x){ return is_variable(x); }))
387 {
388 // Each argument is a variable, this is just an ordinarily defined function
389 return false;
390 }
391
392 // Check whether each variable occurs at most once
393 std::set<variable> variable_set;
394 std::multiset<variable> variable_multiset;
395 find_all_variables(rewrite_rule.lhs(), std::inserter(variable_set, variable_set.end()));
396 find_all_variables(rewrite_rule.lhs(), std::inserter(variable_multiset, variable_multiset.end()));
397 return variable_set.size() == variable_multiset.size();
398}
399
436template <typename StructInfo>
438 const function_symbol& mapping,
439 const data_equation_vector& rewrite_rules,
440 StructInfo& ssf,
443)
444{
445 sort_expression codomain = mapping.sort().target_sort();
446 variable_vector temp_par;
447 if (is_function_sort(mapping.sort()))
448 {
449 const function_sort& sort = atermpp::down_cast<function_sort>(mapping.sort());
450 for (sort_expression s: sort.domain())
451 {
452 temp_par.push_back(variable(id_gen("x"), s));
453 }
454 }
455 variable_list new_parameters(temp_par.begin(), temp_par.end());
456
457 // Create a rule for each data_equation
458 std::vector<detail::rule> rules;
459 for (const data_equation& eq: rewrite_rules)
460 {
461 assert(is_pattern_matching_rule(ssf, eq));
462
463 std::map<data_expression, data_expression> match_criteria;
464 if (is_application(eq.lhs()))
465 {
466 const application& lhs_appl = atermpp::down_cast<application>(eq.lhs());
467
468 assert(lhs_appl.head() == mapping);
469 assert(new_parameters.size() == lhs_appl.size());
470
471 // Simultaneously iterate the parameters defined by the mapping and this
472 // left-hand side to determine how matching occurs
473 auto mappar_it = new_parameters.begin();
474 auto lhspar_it = lhs_appl.begin();
475 while(mappar_it != new_parameters.end())
476 {
477 match_criteria[*mappar_it] = *lhspar_it;
478 ++mappar_it, ++lhspar_it;
479 }
480
481 assert(lhspar_it == lhs_appl.end());
482 }
483
484 detail::rule rule(match_criteria, eq.rhs(), eq.condition(), eq.variables());
485 rules.push_back(rule);
486 }
487#ifdef MCRL2_ENABLE_MACHINENUMBERS
488 assert(rules.size() != 0 || sort_pos::is_most_significant_digit_function_symbol(mapping));
489#else
490 assert(rules.size() != 0);
491#endif
492
493 data_expression new_lhs(application(mapping, new_parameters));
494 data_expression new_rhs(construct_rhs(ssf, gen, rules, codomain));
495 return data_equation(new_parameters, new_lhs, new_rhs);
496}
497
498} // namespace mcrl2::data
499
500#endif
size_type size() const
Returns the size of the term_list.
Definition aterm_list.h:256
const_iterator end() const
Returns a const_iterator pointing to the end of the term_list.
Definition aterm_list.h:282
const_iterator begin() const
Returns a const_iterator pointing to the beginning of the term_list.
Definition aterm_list.h:275
An application of a data expression to a number of arguments.
const_iterator end() const
Returns an iterator pointing past the last argument of the application.
const_iterator begin() const
Returns an iterator pointing to the first argument of the application.
const data_expression & head() const
Get the function at the head of this expression.
std::size_t size() const
\brief A data equation
const data_expression & lhs() const
sort_expression sort() const
Returns the sort of the data expression.
Definition data.cpp:108
\brief A function sort
const sort_expression_list & domain() const
\brief A function symbol
const sort_expression & sort() const
Components for generating an arbitrary element of a sort.
Identifier generator that stores the identifiers of the context in a set. Using the operator()() and ...
void add_identifier(const core::identifier_string &s) override
Adds the identifier s to the context.
\brief A sort expression
const sort_expression & target_sort() const
Returns the target sort of this expression.
\brief A data variable
Definition variable.h:28
Join and split functions for data expressions.
add your file description here.
static RewriterCompilingJitty::substitution_type & sigma(RewriterCompilingJitty *this_rewriter)
add your file description here.
std::string print_map(const MapContainer &v, const std::string &message="")
Creates a string representation of a map.
std::string print_list(const Container &v, const std::string &message="", bool print_index=false, bool boundary_spaces=true)
Creates a string representation of a container.
data_expression construct_rhs(StructInfo &ssf, representative_generator &gen, const std::vector< rule > &rules, const sort_expression &sort)
For a list of rules with equal left hand sides of match_criteria, construct a right hand side.
data_expression construct_condition_rhs(const std::vector< rule > &rules, const data_expression &representative)
For a list of rules with equal left hand sides of match_criteria and only variables in the right hand...
std::ostream & operator<<(std::ostream &os, smt_solver_type s)
standard conversion from solvert type to stream
Definition solver_type.h:71
function_symbol get_top_fs(const data_expression &expr)
Get the top level function symbol f if expr is of the shape f or f(t1,...,tn)
data_expression if_(const data_expression &cond, const data_expression &then, const data_expression &else_)
Returns an expression equivalent to if(cond,then,else_)
const basic_sort & bool_()
Constructor for sort expression Bool.
Definition bool.h:44
bool is_most_significant_digit_function_symbol(const atermpp::aterm &e)
Recogniser for function @most_significant_digit.
Definition pos64.h:215
Namespace for all data library functionality.
Definition abstraction.h:22
std::set< data::variable > find_all_variables(const data::data_expression &x)
Definition data.cpp:94
std::vector< data_equation > data_equation_vector
\brief vector of data_equations
std::vector< variable > variable_vector
\brief vector of variables
Definition variable.h:89
std::vector< data_expression > data_expression_vector
\brief vector of data_expressions
void replace_data_expressions(T &x, const Substitution &sigma, bool innermost, typename std::enable_if<!std::is_base_of< atermpp::aterm, T >::value >::type *=nullptr)
Definition replace.h:190
bool is_function_symbol(const atermpp::aterm &x)
Returns true if the term t is a function symbol.
bool is_pattern_matching_rule(StructInfo &ssf, const data_equation &rewrite_rule)
Check whether the given rewrite rule can be classified as a pattern matching rule.
void find_data_expressions(const T &x, OutputIterator o)
Returns all data expressions that occur in an object.
Definition find.h:423
void replace_variables_capture_avoiding(T &x, Substitution &sigma, data::set_identifier_generator &id_generator, typename std::enable_if<!std::is_base_of< atermpp::aterm, T >::value >::type *=nullptr)
bool is_function_sort(const atermpp::aterm &x)
Returns true if the term t is a function sort.
bool is_application(const atermpp::aterm &x)
Returns true if the term t is an application.
data_equation unfold_pattern_matching(const function_symbol &mapping, const data_equation_vector &rewrite_rules, StructInfo &ssf, representative_generator &gen, set_identifier_generator &id_gen)
This converts a collection of rewrite rules for a give function symbol into a one-rule specification ...
bool is_variable(const atermpp::aterm &x)
Returns true if the term t is a variable.
bool contains(const atermpp::indexed_set< Key, ThreadSafe, Hash, Equals, Allocator, KeyTable > &c, const typename atermpp::indexed_set< Key, ThreadSafe, Hash, Equals, Allocator, KeyTable >::key_type &v, const std::size_t thread_index=0)
Definition indexed_set.h:86
Component for generating representatives of sorts.
A rule describes a partially pattern-matched rewrite rule.
std::map< data_expression, data_expression > match_criteria
rule(const std::map< data_expression, data_expression > &mc, const data_expression &r, const data_expression &c, const variable_list &v)
Generic substitution function. The substitution is stored as a mapping of variables to expressions.
Substitution that maps a single variable to a data expression.