Fawkes API  Fawkes Development Version
map_skill.cpp
1 /***************************************************************************
2  * map_skill.cpp - Skill mapping function
3  *
4  * Created: Tue Sep 26 16:16:14 2017
5  * Copyright 2017 Tim Niemueller [www.niemueller.de]
6  ****************************************************************************/
7 
8 /* This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Library General Public License for more details.
17  *
18  * Read the full text in the LICENSE.GPL file in the doc directory.
19  */
20 
21 #include "map_skill.h"
22 
23 #include <list>
24 
25 namespace fawkes {
26 
27 #define REGEX_PARAM "\\?\\(([a-zA-Z0-9_-]+)((\\|/([^/]+)/([^/]+)/)*)\\)(s|S|i|f|y|Y)"
28 
29 /** @class ActionSkillMapping "map_skill.h"
30  * Class to maintain and perform mapping from actions to skills.
31  * Given an action name and parameters, transform to skill string according to
32  * some predetermined mapping.
33  *
34  *
35  * A mapping is a tuple of two elements:
36  * - parameter key or path element (left of the colon)
37  * - parameter value
38  * These elements are described in the following.
39  *
40  * The configuration key or path element is the PDDL operator name.
41  *
42  * The mapping value can use the following elements as a pattern
43  * modification for the skill string.
44  * Note: parameters are always converted to lower-case by ROSPlan (at least
45  * in the default combination with POPF).
46  *
47  * Variable substitution has the general pattern ?(varname)M, where varname
48  * is the name of the operator parameter and M a modifier. The modifier must
49  * be one of:
50  * - s or S: convert value to string, that is add qutotation marks around
51  * the value. s passes the string as is, S converts to uppercase.
52  * - y or Y: pass value as symbol, i.e., the string value as is without
53  * quotation marks. Y additionally converts to upper case.
54  * - i: converts the value to an integer
55  * - f: converts the value to a float value
56  *
57  * Additionally, the arguments may be modified with a chain of regular
58  * expressions. Then, the expression looks like this:
59  * ?(var|/pattern/replace/|...)
60  * There can be an arbitrary number of regular expressions chained by the
61  * pipe (|) symbol. The "/pattern/replace/" can be a regular expression
62  * according to the C++ ECMAScript syntax.
63  * NOTE: the expressions may contain neither a slash (/) nor a pipe
64  * (|), not even if quoted with a backslash. This is because a rather
65  * simple pattern is used to extract the regex from the argument string.
66  * The replacement string may contain reference to matched groups
67  * (cf. http://ecma-international.org/ecma-262/5.1/ *sec-15.5.4.11). In
68  * particular, the following might be useful:
69  * - $$: an actual dollar sign
70  * - $&: the full matched substring
71  * - $n: the n'th capture (may also be $nn for 10 <= nn <= 99)
72  * Note that regular expression matching is performed case-insensitive, that
73  * is because PDDL itself is also case-insensitive.
74  *
75  *
76  * == Examples ==
77  * Examples contain three elements, the typed PDDL operator name with
78  * parameters, the conversion string, and one or more conversion examples
79  * of grounded actions to actuall skill strings.
80  *
81  * PDDL: (move ?r - robot ?from ?to - location)
82  * Param: move: ppgoto{place=?(to)S}
83  * Examples: (move R-1 START C-BS-I) -> ppgoto{place="C-BS-I"}
84  *
85  * PDDL: (enter-field ?r - robot ?team-color - team-color)
86  * Param: enter-field: drive_into_field{team=?(team-color)S, wait=?(r|/R-1/0.0/|/R-2/3.0/|/R-3/6.0/)f}
87  * Examples: (enter_field R-1 CYAN) -> drive_into_field{team="CYAN", wait=0.000000}
88  * (enter_field R-2 CYAN) -> drive_into_field{team="CYAN", wait=3.000000}
89  * Note: the chaining of regular expressions to fill in a parameter based on
90  * the specific value of another parameter. You can also see that arguments
91  * can be referenced more than once.
92  *
93  * @author Tim Niemueller
94  */
95 
96 /** Constructor. */
98 {
99 }
100 
101 /** Constructor with initial mapping.
102  * @param mappings initial mapping
103  */
104 ActionSkillMapping::ActionSkillMapping(std::map<std::string, std::string> &mappings)
105 : mappings_(mappings)
106 {
107 }
108 
109 /** Add another mapping.
110  * @param action_name name of action to map
111  * @param skill_string_template substitutation template
112  */
113 void
114 ActionSkillMapping::add_mapping(const std::string &action_name,
115  const std::string &skill_string_template)
116 {
117  mappings_[action_name] = skill_string_template;
118 }
119 
120 /** Check if mapping for an action exists.
121  * @param action_name name of action to check
122  * @return true if mapping exists, false otherwise
123  */
124 bool
125 ActionSkillMapping::has_mapping(const std::string &action_name) const
126 {
127  return (mappings_.find(action_name) != mappings_.end());
128 }
129 
130 /** Perform mapping
131  * @param name name of action
132  * @param params parameters as key value pairs
133  * @param messages contains informational and error messages upon return.
134  * The key denotes the severity, e.g., WARN or ERROR, the value is the actual
135  * message.
136  * @return The skill string of the mapped action, or an empty string in case of an error.
137  */
138 std::string
139 ActionSkillMapping::map_skill(const std::string & name,
140  const std::map<std::string, std::string> &params,
141  std::multimap<std::string, std::string> & messages) const
142 {
143  std::string rv;
144 
145  auto mapping = mappings_.find(name);
146  if (mapping == mappings_.end())
147  return "";
148  std::string remainder = mapping->second;
149 
150  std::regex re(REGEX_PARAM);
151  std::smatch m;
152  while (std::regex_search(remainder, m, re)) {
153  bool found = false;
154  for (const auto &p : params) {
155  std::string value = p.second;
156  if (p.first == m[1].str()) {
157  found = true;
158  rv += m.prefix();
159 
160  if (!m[2].str().empty()) {
161  std::string rstr = m[2].str();
162  std::list<std::string> rlst;
163  std::string::size_type rpos = 0, fpos = 0;
164  while ((fpos = rstr.find('|', rpos)) != std::string::npos) {
165  std::string substr = rstr.substr(rpos, fpos - rpos);
166  if (!substr.empty())
167  rlst.push_back(substr);
168  rpos = fpos + 1;
169  }
170  rstr = rstr.substr(rpos);
171  if (!rstr.empty())
172  rlst.push_back(rstr);
173 
174  for (const auto &r : rlst) {
175  if (r.size() > 2 && r[0] == '/' && r[r.size() - 1] == '/') {
176  std::string::size_type slash_pos = r.find('/', 1);
177  if (slash_pos != std::string::npos && slash_pos < (r.size() - 1)) {
178  std::string r_match = r.substr(1, slash_pos - 1);
179  std::string r_repl = r.substr(slash_pos + 1, (r.size() - slash_pos - 2));
180  std::regex user_regex(r_match, std::regex::ECMAScript | std::regex::icase);
181  value = std::regex_replace(value, user_regex, r_repl);
182  } else {
183  messages.insert(
184  std::make_pair("WARN", " regex '" + r + "' missing mid slash, ignoring"));
185  }
186  } else {
187  messages.insert(
188  std::make_pair("WARN", "regex '" + r + "' missing start/end slashes, ignoring"));
189  }
190  }
191  }
192 
193  switch (m[6].str()[0]) {
194  case 's': rv += "\"" + value + "\""; break;
195  case 'S': {
196  std::string uc = value;
197  std::transform(uc.begin(), uc.end(), uc.begin(), ::toupper);
198  rv += "\"" + uc + "\"";
199  } break;
200  case 'y': rv += value; break;
201  case 'Y': {
202  std::string uc = value;
203  std::transform(uc.begin(), uc.end(), uc.begin(), ::toupper);
204  rv += uc;
205  } break;
206  case 'i': try { rv += std::to_string(std::stol(value));
207  } catch (std::invalid_argument &e) {
208  messages.insert(
209  std::make_pair("ERROR", "Failed to convert '" + value + "' to integer: " + e.what()));
210  return "";
211  }
212  break;
213 
214  case 'f': try { rv += std::to_string(std::stod(value));
215  } catch (std::invalid_argument &e) {
216  messages.insert(
217  std::make_pair("ERROR", "Failed to convert '" + value + "' to float: " + e.what()));
218  return "";
219  }
220  break;
221  }
222  break;
223  }
224  }
225  if (!found) {
226  messages.insert(std::make_pair("ERROR",
227  "No value for parameter '" + m[1].str() + "' of action '"
228  + name + "' given"));
229  return "";
230  }
231 
232  remainder = m.suffix();
233  }
234  rv += remainder;
235 
236  return rv;
237 }
238 
239 } // namespace fawkes
fawkes::ActionSkillMapping::has_mapping
bool has_mapping(const std::string &action_name) const
Check if mapping for an action exists.
Definition: map_skill.cpp:129
fawkes
fawkes::ActionSkillMapping::map_skill
std::string map_skill(const std::string &name, const std::map< std::string, std::string > &params, std::multimap< std::string, std::string > &messages) const
Perform mapping.
Definition: map_skill.cpp:143
fawkes::ActionSkillMapping::add_mapping
void add_mapping(const std::string &action_name, const std::string &skill_string_template)
Add another mapping.
Definition: map_skill.cpp:118
fawkes::ActionSkillMapping::ActionSkillMapping
ActionSkillMapping()
Constructor.
Definition: map_skill.cpp:101