Doxygen
Loading...
Searching...
No Matches
aliases.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Copyright (C) 1997-2023 by Dimitri van Heesch.
4 *
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation under the terms of the GNU General Public License is hereby
7 * granted. No representations are made about the suitability of this software
8 * for any purpose. It is provided "as is" without express or implied warranty.
9 * See the GNU General Public License for more details.
10 *
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
13 *
14 */
15
16// own include
17#include "aliases.h"
18
19// standard includes
20#include <unordered_map>
21
22// other includes
23#include "config.h"
24#include "debug.h"
25#include "message.h"
26#include "regex.h"
27#include "stringutil.h"
28#include "util.h"
29
30//-----------------------------------------------------------
31
33{
34 AliasInfo(const std::string &val,const std::string &sep=std::string())
35 : value(val), separator(sep) {}
36 std::string value;
37 std::string separator;
38};
39
40using AliasOverloads = std::unordered_map<int,AliasInfo>; // key = parameter count
41using AliasInfoMap = std::unordered_map<std::string,AliasOverloads>; // key = alias name (with parameter part)
42
43//-----------------------------------------------------------
44
45static std::string expandAliasRec(StringUnorderedSet &aliasesProcessed,
46 std::string_view s,bool allowRecursion=false);
47static int countAliasArguments(std::string_view args, std::string_view sep);
48static std::string extractAliasArgs(std::string_view args);
49static std::string expandAlias(std::string_view aliasName,std::string_view aliasValue);
50
51//-----------------------------------------------------------
52
54
55//-----------------------------------------------------------
56
57static void addValidAliasToMap(std::string_view alias)
58{
59 bool valid = true;
60 std::string aliasName;
61 std::string aliasValue;
62 int numParams = 0;
63 std::string separator;
64
65 static std::string_view separators = "!#$%&,.?|;:'+=~`/";
66 auto isValidSeparator = [](char c) -> bool { return separators.find(c)!=std::string::npos; };
67
68 static const reg::Ex re(R"(^(\a[\w-]*)({[^}]*})?\s*=)");
69 reg::Match m;
70 if (reg::search(alias,m,re)) // valid name= or name{...}= part
71 {
72 size_t i=m.length();
73 ASSERT(i!=std::string::npos); // based on re is always a =
74 ASSERT(m.size()==3); // m[0]=full match including '=', m[1]=name, m[2]=optional params
75 aliasName = m[1].str();
76 aliasValue = alias.substr(i);
77 //printf("Alias: found name='%s' value='%s'\n",qPrint(name),qPrint(aliasValue));
78 if (!m[2].empty()) // alias with parameters
79 {
80 separator=",";
81 size_t b = m[2].position(); // index of '{'
82 size_t e = b + m[2].length(); // index of '}'
83 size_t k=b+1;
84 while (k<e-1 && isdigit(alias[k])) k++;
85 numParams = atoi(std::string{alias.substr(b+1,k-b-1)}.c_str());
86 if (numParams>0)
87 {
88 if (k<e-1) // we have a separator
89 {
90 size_t s=k;
91 while (s<e && isValidSeparator(alias[s])) s++;
92 if (s<e-1)
93 {
94 err("Invalid alias '{}': invalid separator character '{:c}' (code {:d}), allowed characters: {}. Check your config file.\n",alias,alias[s],alias[s],separators);
95 valid=false;
96 }
97 else
98 {
99 separator=alias.substr(k,e-k-1);
100 }
101 }
102 if (valid) // valid alias with parameters
103 {
104 Debug::print(Debug::Alias,0,"Alias definition: name='{}' #param='{}' separator='{}' value='{}'\n",
105 aliasName,numParams,separator,aliasValue);
106 }
107 }
108 else
109 {
110 err("Invalid alias '{}': missing number of parameters. Check your config file.\n",alias);
111 valid=false;
112 }
113 }
114 else // valid alias without parameters
115 {
116 numParams = 0;
117 Debug::print(Debug::Alias,0,"Alias definition: name='{}' value='{}'\n",aliasName,aliasValue);
118 }
119 }
120 else
121 {
122 err("Invalid alias '{}': invalid 'name=' or 'name{{...}}=' part. Check you config file.\n",alias);
123 valid=false;
124 }
125
126 if (valid) // alias definition passed all checks, so store it.
127 {
128 auto it = g_aliasInfoMap.find(aliasName);
129 if (it==g_aliasInfoMap.end()) // insert new alias
130 {
131 AliasOverloads overloads { { numParams, AliasInfo(aliasValue, separator) } };
132 g_aliasInfoMap.emplace(aliasName,overloads);
133 }
134 else // replace exiting alias with new definition
135 {
136 auto it2 = it->second.find(numParams);
137 if (it2==it->second.end()) // new alias overload for the given number of parameters
138 {
139 it->second.emplace(numParams, AliasInfo(aliasValue,separator));
140 }
141 else // replace alias with new definition
142 {
143 it2->second = AliasInfo(aliasValue,separator);
144 }
145 }
146 }
147}
148
149
150//----------------------------------------------------------------------------
151
152static std::string escapeAlias(std::string_view value)
153{
154 std::string newValue = substituteStringView(value,"^^ ","@ilinebr ");
155 newValue = substituteStringView(newValue,"^^","@ilinebr ");
156 //printf("escapeAlias('%s')='%s'\n",qPrint(std::string{value}),qPrint(newValue));
157 return newValue;
158}
159
160//----------------------------------------------------------------------------
161
163{
164 StringVector aliases = Config_getList(ALIASES);
165 // add aliases to a dictionary
166 for (const auto &al : aliases)
167 {
169 }
170 for (auto &[name,overloads] : g_aliasInfoMap)
171 {
172 for (auto &[numParams,aliasInfo] : overloads)
173 {
174 aliasInfo.value = expandAlias(name+":"+std::to_string(numParams),aliasInfo.value);
175 }
176 }
177 for (auto &[name,overloads] : g_aliasInfoMap)
178 {
179 for (auto &[numParams,aliasInfo] : overloads)
180 {
181 aliasInfo.value = escapeAlias(aliasInfo.value);
182 }
183 }
184}
185
186//--------------------------------------------------------------------------------------
187
188struct Marker
189{
190 Marker(size_t p, size_t n,size_t s) : pos(p), number(n), size(s) {}
191 size_t pos; // position in the string
192 size_t number; // argument number
193 size_t size; // size of the marker
194};
195
196/** For a string \a s that starts with a command name, returns the character
197 * offset within that string representing the first character after the
198 * command. For an alias with argument, this is the offset to the
199 * character just after the argument list.
200 *
201 * Examples:
202 * - s=="a b" returns 1
203 * - s=="a{2,3} b" returns 6
204 * = s=="#" returns 0
205 */
206static size_t findEndOfCommand(std::string_view s)
207{
208 size_t i = 0;
209 while (i < s.size() && isId(s[i])) ++i;
210 if (i < s.size() && s[i] == '{')
211 {
212 i += extractAliasArgs(s.substr(i)).length() + 2; // +2 for '{' and '}'
213 }
214 return i;
215}
216
217/** Replaces the markers in an alias definition \a aliasValue
218 * with the corresponding values found in the comma separated argument
219 * list \a argList and the returns the result after recursive alias expansion.
220 */
221static std::string replaceAliasArguments(StringUnorderedSet &aliasesProcessed,
222 std::string_view aliasValue,std::string_view argList,
223 std::string_view sep)
224{
225 //printf("----- replaceAliasArguments(val=[%s],args=[%s],sep=[%s])\n",qPrint(aliasValue),qPrint(argList),qPrint(sep));
226
227 // first make a list of arguments from the comma separated argument list
228 StringViewVector args;
229 size_t l=argList.length();
230 size_t p=0;
231 for (size_t i=0;i<l;i++)
232 {
233 char c = argList[i];
234 if (!sep.empty() &&
235 c==sep[0] && // start with separator character
236 (i==0 || argList[i-1]!='\\') && // is not escaped
237 argList.substr(i,sep.length())==sep) // whole separator matches
238 {
239 args.push_back(argList.substr(p,i-p));
240 p = i+sep.length(); // start of next argument
241 i = p-1; // compensate with -1 for loop iterator
242 }
243 else if (c=='@' || c=='\\') // command
244 {
245 // check if this is the start of another aliased command (see bug704172)
246 i+=findEndOfCommand(argList.substr(i+1));
247 }
248 }
249 if (l>p) args.push_back(argList.substr(p));
250 //printf("found %zu arguments\n",args.size());
251
252 // next we look for the positions of the markers and add them to a list
253 std::vector<Marker> markerList;
254 l = aliasValue.length();
255 char pc = '\0';
256 bool insideMarkerId = false;
257 size_t markerStart = 0;
258 auto isDigit = [](char c) { return c>='0' && c<='9'; };
259 for (size_t i=0;i<=l;i++)
260 {
261 char c = i<l ? aliasValue[i] : '\0';
262 if (insideMarkerId && !isDigit(c)) // found end of a markerId
263 {
264 insideMarkerId = false;
265 size_t markerLen = i-markerStart;
266 markerList.emplace_back(markerStart-1,
267 static_cast<size_t>(std::stoi(std::string{aliasValue.substr(markerStart,markerLen)})),
268 markerLen+1);
269 }
270 if (c=='\\' && (pc=='@' || pc=='\\')) // found escaped backslash
271 {
272 // skip
273 pc = '\0';
274 }
275 else
276 {
277 if (isDigit(c) && pc=='\\') // found start of a markerId
278 {
279 insideMarkerId=true;
280 markerStart=i;
281 }
282 pc = c;
283 }
284 }
285
286 // then we replace the markers with the corresponding arguments in one pass
287 std::string result;
288 p = 0;
289 for (const Marker &m : markerList)
290 {
291 result+=aliasValue.substr(p,m.pos-p);
292 //printf("part before marker: '%s'\n",qPrint(aliasValue.substr(p,m.pos-p)));
293 if (m.number>0 && m.number<=args.size()) // valid number
294 {
295 result+=expandAliasRec(aliasesProcessed,args[m.number-1],true);
296 //printf("marker index=%zu pos=%zu number=%zu size=%zu replacement %s\n",i,m.pos,m.number,m.size,
297 // qPrint(args[m.number-1]));
298 }
299 p=m.pos+m.size; // continue after the marker
300 }
301 result+=aliasValue.substr(p); // append remainder
302 //printf("string after replacement of markers: '%s'\n",qPrint(result));
303
304 // expand the result again
305 substituteInplace(result,"\\{","{");
306 substituteInplace(result,"\\}","}");
307 substituteInplace(result,std::string{"\\"}+std::string{sep},sep);
308 result = expandAliasRec(aliasesProcessed,result);
309
310 //printf("final string '%s'\n",qPrint(result));
311 return result;
312}
313
314static std::string escapeSeparators(const std::string &s, const std::string &sep)
315{
316 if (s.empty() || sep.empty()) return s;
317 std::string result;
318 result.reserve(s.length()+10);
319 size_t i, p=0, l=sep.length();
320 while ((i=s.find(sep,p))!=std::string::npos)
321 {
322 result += s.substr(p,i-p);
323 if (i>0 && s[i-1]!='\\') // escape the separator
324 {
325 result += '\\';
326 }
327 result += s.substr(i,l);
328 p = i+l;
329 }
330 result += s.substr(p);
331 //printf("escapeSeparators(%s,sep='%s')=%s\n",qPrint(s),qPrint(sep),qPrint(result));
332 return result;
333}
334
335static std::string expandAliasRec(StringUnorderedSet &aliasesProcessed,std::string_view s,bool allowRecursion)
336{
337 std::string result;
338 static const reg::Ex re(R"([\\@](\a[\w-]*))");
339 std::string str{s};
340 reg::Match match;
341 size_t p = 0;
342 while (reg::search(str,match,re,p))
343 {
344 size_t i = match.position();
345 size_t l = match.length();
346 if (i>p) result+=s.substr(p,i-p);
347
348 std::string args = extractAliasArgs(s.substr(i+l));
349 bool hasArgs = !args.empty(); // found directly after command
350 size_t argsLen = args.length();
351 std::string cmd = match[1].str();
352 int selectedNumArgs = -1;
353 //printf("looking for alias '%s' with params '%s'\n",qPrint(cmd),qPrint(args));
354 auto it = g_aliasInfoMap.find(cmd);
355 if (it == g_aliasInfoMap.end())
356 {
357 // if command has a - then also try part in without it
358 size_t minusPos = cmd.find('-');
359 if (minusPos!=std::string::npos)
360 {
361 it = g_aliasInfoMap.find(cmd.substr(0,minusPos));
362 if (it!=g_aliasInfoMap.end()) // found part before - as alias
363 {
364 cmd = cmd.substr(0,minusPos);
365 args = "";
366 hasArgs = false;
367 argsLen = 0;
368 l = cmd.length()+1; // +1 for the minus sign
369 }
370 }
371 }
372 if (it != g_aliasInfoMap.end()) // cmd is an alias
373 {
374 //printf("found an alias, hasArgs=%d\n",hasArgs);
375 if (hasArgs)
376 {
377 // Find the an alias that matches the number of arguments.
378 // If there are multiple candidates, take the one that matches the most parameters
379 for (const auto &[numParams,aliasInfo] : it->second)
380 {
381 int numArgs = countAliasArguments(args,aliasInfo.separator);
382 if (numParams==numArgs && numArgs>selectedNumArgs)
383 {
384 selectedNumArgs = numArgs;
385 }
386 }
387 if (selectedNumArgs==-1) // no match found, check if there is an alias with one argument
388 {
389 auto it2 = it->second.find(1);
390 if (it2 != it->second.end())
391 {
392 args = escapeSeparators(args,it2->second.separator); // escape separator so that everything is seen as one argument
393 selectedNumArgs = 1;
394 }
395 }
396 }
397 else
398 {
399 selectedNumArgs = 0;
400 }
401 }
402 else
403 {
404 //printf("Alias %s not found\n",qPrint(cmd));
405 }
406 //printf("Found command s='%s' cmd='%s' numArgs=%d args='%s'\n", qPrint(s),qPrint(cmd),selectedNumArgs,qPrint(args));
407 std::string qualifiedName = cmd+":"+std::to_string(selectedNumArgs);
408 if ((allowRecursion || aliasesProcessed.find(qualifiedName)==aliasesProcessed.end()) &&
409 it!=g_aliasInfoMap.end() && selectedNumArgs!=-1 &&
410 it->second.find(selectedNumArgs)!=it->second.end()) // expand the alias
411 {
412 const auto &aliasInfo = it->second.find(selectedNumArgs)->second;
413 //printf("is an alias with separator='%s' selectedNumArgs=%d hasArgs=%d!\n",qPrint(aliasInfo.separator),selectedNumArgs,hasArgs);
414 if (!allowRecursion) aliasesProcessed.insert(qualifiedName);
415 std::string val = aliasInfo.value;
416 if (hasArgs)
417 {
418 //printf("before replaceAliasArguments(val='%s')\n",qPrint(val));
419 val = replaceAliasArguments(aliasesProcessed,val,args,aliasInfo.separator);
420 //printf("after replaceAliasArguments sep='%s' val='%s' args='%s'\n",
421 // qPrint(aliasInfo.separator),qPrint(val),qPrint(args));
422 }
423 result += expandAliasRec(aliasesProcessed,val);
424 if (!allowRecursion) aliasesProcessed.erase(qualifiedName);
425 p = i+l;
426 if (hasArgs) p += argsLen+2;
427 }
428 else // command is not an alias
429 {
430 //printf("not an alias!\n");
431 result += match.str();
432 p = i+l;
433 }
434 }
435 result += s.substr(p);
436 //printf("expandAliases \"%s\"->\"%s\"\n",qPrint(s),qPrint(result));
437 return result;
438}
439
440
441static int countAliasArguments(std::string_view args, std::string_view sep)
442{
443 int count = 1;
444 size_t l = args.length();
445 for (size_t i=0;i<l;i++)
446 {
447 char c = args[i];
448 if (!sep.empty() &&
449 c==sep[0] && // start with separator character
450 (i==0 || args[i-1]!='\\') && // is not escaped
451 args.substr(i,sep.length())==sep) // whole separator matches
452 {
453 count++;
454 }
455 else if (c=='@' || c=='\\')
456 {
457 // check if this is the start of another aliased command (see bug704172)
458 i += findEndOfCommand(args.substr(i+1));
459 }
460 }
461 //printf("countAliasArguments(%s,sep=%s)=%d\n",qPrint(args),qPrint(sep),count);
462 return count;
463}
464
465static std::string extractAliasArgs(std::string_view args)
466{
467 int bc = 0;
468 char prevChar = 0;
469 if (!args.empty() && args[0]=='{') // alias has argument
470 {
471 for (size_t i=0;i<args.length();i++)
472 {
473 char c = args[i];
474 if (prevChar!='\\') // not escaped
475 {
476 if (c=='{') bc++;
477 if (c=='}') bc--;
478 prevChar=c;
479 }
480 else
481 {
482 prevChar=0;
483 }
484
485 if (bc==0)
486 {
487 //printf("extractAliasArgs('%s')->'%s'\n",qPrint(args),qPrint(args.substr(1,i-1)));
488 return std::string{args.substr(1,i-1)};
489 }
490 }
491 }
492 return std::string{};
493}
494
495std::string resolveAliasCmd(std::string_view aliasCmd)
496{
497 StringUnorderedSet aliasesProcessed;
498 //printf("Expanding: '%s'\n",qPrint(aliasCmd));
499 std::string result = expandAliasRec(aliasesProcessed,aliasCmd);
500 //printf("Expanding result: '%s'->'%s'\n",qPrint(aliasCmd),qPrint(result));
501 Debug::print(Debug::Alias,0,"Resolving alias: cmd='{}' result='{}'\n",std::string{aliasCmd},result);
502 return result;
503}
504
505static std::string expandAlias(std::string_view aliasName,std::string_view aliasValue)
506{
507 std::string result;
508 StringUnorderedSet aliasesProcessed;
509 // avoid expanding this command recursively
510 aliasesProcessed.insert(std::string{aliasName});
511 // expand embedded commands
512 //printf("Expanding: '%s'->'%s'\n",qPrint(aliasName),qPrint(aliasValue));
513 result = expandAliasRec(aliasesProcessed,aliasValue);
514 //printf("Expanding result: '%s'->'%s'\n",qPrint(aliasName),qPrint(result));
515 Debug::print(Debug::Alias,0,"Expanding alias: input='{}' result='{}'\n",std::string{aliasValue},result);
516 return result;
517}
518
519bool isAliasCmd(std::string_view aliasCmd)
520{
521 return g_aliasInfoMap.find(std::string{aliasCmd}) != g_aliasInfoMap.end();
522}
bool isAliasCmd(std::string_view aliasCmd)
Definition aliases.cpp:519
static std::string replaceAliasArguments(StringUnorderedSet &aliasesProcessed, std::string_view aliasValue, std::string_view argList, std::string_view sep)
Replaces the markers in an alias definition aliasValue with the corresponding values found in the com...
Definition aliases.cpp:221
static size_t findEndOfCommand(std::string_view s)
For a string s that starts with a command name, returns the character offset within that string repre...
Definition aliases.cpp:206
static std::string expandAlias(std::string_view aliasName, std::string_view aliasValue)
Definition aliases.cpp:505
std::string resolveAliasCmd(std::string_view aliasCmd)
Definition aliases.cpp:495
std::unordered_map< int, AliasInfo > AliasOverloads
Definition aliases.cpp:40
static AliasInfoMap g_aliasInfoMap
Definition aliases.cpp:53
static std::string escapeSeparators(const std::string &s, const std::string &sep)
Definition aliases.cpp:314
std::unordered_map< std::string, AliasOverloads > AliasInfoMap
Definition aliases.cpp:41
static void addValidAliasToMap(std::string_view alias)
Definition aliases.cpp:57
static std::string expandAliasRec(StringUnorderedSet &aliasesProcessed, std::string_view s, bool allowRecursion=false)
Definition aliases.cpp:335
static std::string extractAliasArgs(std::string_view args)
Definition aliases.cpp:465
void readAliases()
Definition aliases.cpp:162
static int countAliasArguments(std::string_view args, std::string_view sep)
Definition aliases.cpp:441
static std::string escapeAlias(std::string_view value)
Definition aliases.cpp:152
@ Alias
Definition debug.h:47
static void print(DebugMask mask, int prio, fmt::format_string< Args... > fmt, Args &&... args)
Definition debug.h:78
Class representing a regular expression.
Definition regex.h:39
Object representing the matching results.
Definition regex.h:154
size_t size() const
Returns the number of sub matches available in this match.
Definition regex.h:185
size_t position() const
Returns the position of the match or std::string::npos if no position is set.
Definition regex.h:160
std::string str() const
Return a string representing the matching part.
Definition regex.h:166
size_t length() const
Returns the position of the match or std::string::npos if no length is set.
Definition regex.h:163
#define Config_getList(name)
Definition config.h:38
std::unordered_set< std::string > StringUnorderedSet
Definition containers.h:29
std::vector< std::string_view > StringViewVector
Definition containers.h:34
std::vector< std::string > StringVector
Definition containers.h:33
bool isId(char c)
Returns true if c is a valid character for an identifier.
Definition dstring.h:895
#define err(fmt,...)
Definition message.h:127
#define ASSERT(x)
Definition message.h:142
bool search(std::string_view str, Match &match, const Ex &re, size_t pos)
Search in a given string str starting at position pos for a match against regular expression re.
Definition regex.cpp:850
Some helper functions for std::string.
std::string substituteStringView(std::string_view s, std::string_view toReplace, std::string_view replaceWith)
Returns a new string where occurrences of substring toReplace in string s are replaced by string repl...
Definition stringutil.h:53
void substituteInplace(std::string &s, std::string_view toReplace, std::string_view replaceWith)
Replaces occurrences of substring toReplace in string s with string replaceWith.
Definition stringutil.h:32
AliasInfo(const std::string &val, const std::string &sep=std::string())
Definition aliases.cpp:34
std::string value
Definition aliases.cpp:36
std::string separator
Definition aliases.cpp:37
size_t size
Definition aliases.cpp:193
size_t number
Definition aliases.cpp:192
Marker(size_t p, size_t n, size_t s)
Definition aliases.cpp:190
size_t pos
Definition aliases.cpp:191
A bunch of utility functions.