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
25namespace 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 */
104ActionSkillMapping::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 */
113void
114ActionSkillMapping::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 */
124bool
125ActionSkillMapping::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 */
138std::string
139ActionSkillMapping::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
void add_mapping(const std::string &action_name, const std::string &skill_string_template)
Add another mapping.
Definition: map_skill.cpp:114
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:139
bool has_mapping(const std::string &action_name) const
Check if mapping for an action exists.
Definition: map_skill.cpp:125
ActionSkillMapping()
Constructor.
Definition: map_skill.cpp:97
Fawkes library namespace.