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