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