Doxygen
Loading...
Searching...
No Matches
regex.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Copyright (C) 1997-2025 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 "regex.h"
17#include <cstdint>
18#include <vector>
19#include <cctype>
20#include <cassert>
21#include <algorithm>
22
23#define ENABLE_DEBUG 0
24#if ENABLE_DEBUG
25#define DBG(fmt,...) do { fprintf(stderr,fmt,__VA_ARGS__); } while(0)
26#else
27#define DBG(fmt,...) do {} while(0)
28#endif
29
30namespace reg
31{
32
33static inline bool isspace(char c)
34{
35 return c==' ' || c=='\t' || c=='\n' || c=='\r';
36}
37
38static inline bool isalpha(char c)
39{
40 return static_cast<unsigned char>(c)>=128 || (c>='a' && c<='z') || (c>='A' && c<='Z');
41}
42
43static inline bool isdigit(char c)
44{
45 return c>='0' && c<='9';
46}
47
48static inline bool isalnum(char c)
49{
50 return isalpha(c) || isdigit(c);
51}
52
53
54/** Class representing a token in the compiled regular expression token stream.
55 * A token has a kind and an optional value whose meaning depends on the kind.
56 * It is also possible to store a (from,to) character range in a token.
57 */
58class PToken
59{
60 public:
61 /** The kind of token.
62 *
63 * Ranges per bit mask:
64 * - `0x00FF` from part of a range, except for `0x0000` which is the End marker
65 * - `0x1FFF` built-in ranges
66 * - `0x2FFF` user defined ranges
67 * - `0x4FFF` special operations
68 * - `0x8000` literal character
69 */
70 enum class Kind : uint16_t
71 {
72 End = 0x0000,
73 WhiteSpace = 0x1001, // \s range [ \t\r\n]
74 Digit = 0x1002, // \d range [0-9]
75 Alpha = 0x1003, // \a range [a-z_A-Z\x80-\xFF]
76 AlphaNum = 0x1004, // \w range [a-Z_A-Z0-9\x80-\xFF]
77 CharClass = 0x2001, // []
78 NegCharClass = 0x2002, // [^]
79 BeginOfLine = 0x4001, // ^
80 EndOfLine = 0x4002, // $
81 BeginOfWord = 0x4003, // <
82 EndOfWord = 0x4004, // >
83 BeginCapture = 0x4005, // (
84 EndCapture = 0x4006, // )
85 Any = 0x4007, // .
86 Star = 0x4008, // *
87 Optional = 0x4009, // ?
88 Character = 0x8000 // c
89 };
90
91 /** returns a string representation of the tokens kind (useful for debugging). */
92 const char *kindStr() const
93 {
94 if ((m_rep>>16)>=0x1000 || m_rep==0)
95 {
96 switch(static_cast<Kind>((m_rep>>16)))
97 {
98 case Kind::End: return "End";
99 case Kind::Alpha: return "Alpha";
100 case Kind::AlphaNum: return "AlphaNum";
101 case Kind::WhiteSpace: return "WhiteSpace";
102 case Kind::Digit: return "Digit";
103 case Kind::CharClass: return "CharClass";
104 case Kind::NegCharClass: return "NegCharClass";
105 case Kind::Character: return "Character";
106 case Kind::BeginOfLine: return "BeginOfLine";
107 case Kind::EndOfLine: return "EndOfLine";
108 case Kind::BeginOfWord: return "BeginOfWord";
109 case Kind::EndOfWord: return "EndOfWord";
110 case Kind::BeginCapture: return "BeginCapture";
111 case Kind::EndCapture: return "EndCapture";
112 case Kind::Any: return "Any";
113 case Kind::Star: return "Star";
114 case Kind::Optional: return "Optional";
115 }
116 }
117 else
118 {
119 return "Range";
120 }
121 }
122
123 /** Creates a token of kind 'End' */
124 PToken() : m_rep(0) {}
125
126 /** Creates a token of the given kind \a k */
127 explicit PToken(Kind k) : m_rep(static_cast<uint32_t>(k)<<16) {}
128
129 /** Create a token for an ASCII character */
130 PToken(char c) : m_rep((static_cast<uint32_t>(Kind::Character)<<16) |
131 static_cast<uint32_t>(c)) {}
132
133 /** Create a token for a byte of an UTF-8 character */
134 PToken(uint16_t v) : m_rep((static_cast<uint32_t>(Kind::Character)<<16) |
135 static_cast<uint32_t>(v)) {}
136
137 /** Create a token representing a range from one character \a from to another character \a to */
138 PToken(uint16_t from,uint16_t to) : m_rep(static_cast<uint32_t>(from)<<16 | to) {}
139
140 /** Sets the value for a token */
141 void setValue(uint16_t value) { m_rep = (m_rep & 0xFFFF0000) | value; }
142
143 /** Returns the kind of the token */
144 Kind kind() const { return static_cast<Kind>(m_rep>>16); }
145
146 /** Returns the 'from' part of the character range. Only valid if this token represents a range */
147 uint16_t from() const { return m_rep>>16; }
148
149 /** Returns the 'to' part of the character range. Only valid if this token represents a range */
150 uint16_t to() const { return m_rep & 0xFFFF; }
151
152 /** Returns the value for this token */
153 uint16_t value() const { return m_rep & 0xFFFF; }
154
155 /** Returns the value for this token as a ASCII character */
156 char asciiValue() const { return static_cast<char>(m_rep); }
157
158 /** Returns true iff this token represents a range of characters */
159 bool isRange() const { return m_rep!=0 && from()<=to(); }
160
161 /** Returns true iff this token is a positive or negative character class */
162 bool isCharClass() const { return kind()==Kind::CharClass || kind()==Kind::NegCharClass; }
163
164 private:
165 uint32_t m_rep;
166};
167
168/** Private members of a regular expression */
170{
171 public:
172 /** Creates the private part */
173 Private(std::string_view pat) : pattern(pat)
174 {
175 data.reserve(100);
176 }
177 void compile();
178#if ENABLE_DEBUG
179 void dump();
180#endif
181 bool matchAt(size_t tokenPos,size_t tokenLen,std::string_view str,
182 Match &match,size_t pos,int level) const;
183
184 /** Flag indicating the expression was successfully compiled */
185 bool error = false;
186
187 /** The token stream representing the compiled regular expression. */
188 std::vector<PToken> data; // compiled pattern
189
190 /** The pattern string as passed by the user */
191 std::string pattern;
192
193 /** Number of capture groups in the pattern (excluding the whole match) */
194 size_t captureCount = 0;
195};
196
197/** Compiles a regular expression passed as a string into a stream of tokens that can be used for
198 * efficient searching.
199 */
201{
202 error = false;
203 data.clear();
204 captureCount = 0;
205 if (pattern.empty()) return;
206 const char *start = pattern.c_str();
207 const char *ps = start;
208 char c = 0;
209
210 int prevTokenPos=-1;
211 int tokenPos=0;
212
213 // capture group assignment
214 std::vector<size_t> captureStack;
215 size_t nextCaptureId = 0;
216
217 auto addToken = [&](PToken tok)
218 {
219 tokenPos++;
220 data.emplace_back(tok);
221 };
222
223 auto getNextCharacter = [&]() -> PToken
224 {
225 char cs=*ps;
226 PToken result = PToken(cs);
227 if (cs=='\\') // escaped character
228 {
229 ps++;
230 cs=*ps;
231 switch (cs)
232 {
233 case 'n': result = PToken('\n'); break;
234 case 'r': result = PToken('\r'); break;
235 case 't': result = PToken('\t'); break;
236 case 's': result = PToken(PToken::Kind::WhiteSpace); break;
237 case 'a': result = PToken(PToken::Kind::Alpha); break;
238 case 'w': result = PToken(PToken::Kind::AlphaNum); break;
239 case 'd': result = PToken(PToken::Kind::Digit); break;
240 case '<': result = PToken(PToken::Kind::BeginOfWord); break;
241 case '>': result = PToken(PToken::Kind::EndOfWord); break;
242 case 'x':
243 case 'X':
244 {
245 uint16_t v=0;
246 for (int i=0;i<2 && (cs=(*(ps+1)));i++) // 2 hex digits
247 {
248 int d = (cs>='a' && cs<='f') ? cs-'a'+10 :
249 (cs>='A' && cs<='F') ? cs-'A'+10 :
250 (cs>='0' && cs<='9') ? cs-'0' :
251 -1;
252 if (d>=0) { v<<=4; v|=d; ps++; } else break;
253 }
254 result = PToken(v);
255 }
256 break;
257 case '\0': ps--; break; // backslash at the end of the pattern
258 default:
259 result = PToken(cs);
260 break;
261 }
262 }
263 return result;
264 };
265
266 while ((c=*ps))
267 {
268 switch (c)
269 {
270 case '^': // beginning of line (if first character of the pattern)
271 prevTokenPos = tokenPos;
272 addToken(ps==start ? PToken(PToken::Kind::BeginOfLine) :
273 PToken(c));
274 break;
275 case '$': // end of the line (if last character of the pattern)
276 prevTokenPos = tokenPos;
277 addToken(*(ps+1)=='\0' ? PToken(PToken::Kind::EndOfLine) :
278 PToken(c));
279 break;
280 case '.': // any character
281 prevTokenPos = tokenPos;
282 addToken(PToken(PToken::Kind::Any));
283 break;
284 case '(': // begin of capture group
285 {
286 prevTokenPos = tokenPos;
288 size_t id = ++nextCaptureId; // groups start at 1, 0 is whole match
289 data.back().setValue(static_cast<uint16_t>(id));
290 captureStack.push_back(id);
291 }
292 break;
293 case ')': // end of capture group
294 {
295 prevTokenPos = tokenPos;
296 if (captureStack.empty())
297 {
298 error=true;
299 return;
300 }
301 size_t id = captureStack.back();
302 captureStack.pop_back();
304 data.back().setValue(static_cast<uint16_t>(id));
305 }
306 break;
307 case '[': // character class
308 {
309 prevTokenPos = tokenPos;
310 ps++;
311 if (*ps==0) { error=true; return; }
312 bool esc = *ps=='\\';
313 PToken tok = getNextCharacter();
314 ps++;
315 if (!esc && tok.kind()==PToken::Kind::Character &&
316 tok.asciiValue()=='^') // negated character class
317 {
319 if (*ps==0) { error=true; return; }
320 tok = getNextCharacter();
321 ps++;
322 }
323 else
324 {
326 }
327 uint16_t numTokens=0;
328 while ((c=*ps))
329 {
330 if (c=='-' && *(ps+1)!=']' && *(ps+1)!=0) // range
331 {
332 getNextCharacter();
333 ps++;
334 PToken endTok = getNextCharacter();
335 ps++;
336 if (tok.value()>endTok.value())
337 {
338 addToken(PToken(endTok.value(),tok.value())); // swap start and end
339 }
340 else
341 {
342 addToken(PToken(tok.value(),endTok.value()));
343 }
344 numTokens++;
345 }
346 else // single char, from==to
347 {
348 if (tok.kind()==PToken::Kind::Character)
349 {
350 addToken(PToken(tok.value(),tok.value()));
351 }
352 else // special token, add as-is since from>to
353 {
354 addToken(tok);
355 }
356 numTokens++;
357 }
358 if (*ps==0) { error=true; return; } // expected at least a ]
359 esc = *ps=='\\';
360 tok = getNextCharacter();
361 if (!esc && tok.kind()==PToken::Kind::Character &&
362 tok.value()==static_cast<uint16_t>(']'))
363 {
364 break; // end of character class
365 }
366 if (*ps==0) { error=true; return; } // no ] found
367 ps++;
368 }
369 // set the value of either NegCharClass or CharClass
370 data[prevTokenPos].setValue(numTokens);
371 }
372 break;
373 case '*': // 0 or more
374 case '+': // 1 or more
375 case '?': // optional: 0 or 1
376 {
377 if (prevTokenPos==-1)
378 {
379 error=true;
380 return;
381 }
382 switch (data[prevTokenPos].kind())
383 {
384 case PToken::Kind::BeginOfLine: // $* or $+ or $?
385 case PToken::Kind::BeginOfWord: // <* or <+ or <?
386 case PToken::Kind::EndOfWord: // >* or >+ or >?
387 case PToken::Kind::Star: // ** or *+ or *?
388 case PToken::Kind::Optional: // ?* or ?+ or ??
389 error=true;
390 return;
391 default: // ok
392 break;
393 }
394 int ddiff = static_cast<int>(tokenPos-prevTokenPos);
395 if (*ps=='+') // convert <pat>+ -> <pat><pat>*
396 {
397 // turn a sequence of token [T1...Tn] followed by '+' into [T1..Tn T1..Tn T*]
398 // ddiff=n ^prevTokenPos
399 data.resize(data.size()+ddiff);
400 std::copy_n(data.begin()+prevTokenPos,ddiff,data.begin()+tokenPos);
401 prevTokenPos+=ddiff;
402 tokenPos+=ddiff;
403 }
404 if (data[prevTokenPos].kind()==PToken::Kind::EndCapture)
405 {
406 // find the beginning of the capture range, accounting for nesting
407 int depth = 1;
408 while (prevTokenPos>0 && depth>0)
409 {
410 prevTokenPos--;
411 if (data[prevTokenPos].kind()==PToken::Kind::EndCapture) depth++;
412 else if (data[prevTokenPos].kind()==PToken::Kind::BeginCapture) depth--;
413 }
414 }
415 data.insert(data.begin()+prevTokenPos,
417 tokenPos++;
418 addToken(PToken(PToken::Kind::End));
419 // turn a sequence of tokens [T1 T2 T3] followed by 'T*' or into [T* T1 T2 T3 TEND]
420 // ^prevTokenPos
421 // same for 'T?'.
422 }
423 break;
424 default:
425 prevTokenPos = tokenPos;
426 addToken(getNextCharacter());
427 break;
428 }
429 ps++;
430 }
431 if (!captureStack.empty()) // Unmatched '('?
432 {
433 error=true;
434 return;
435 }
436 captureCount = nextCaptureId;
437 //addToken(PToken(PToken::Kind::End));
438}
439
440#if ENABLE_DEBUG
441/** Dump the compiled token stream for this regular expression. For debugging purposes. */
442void Ex::Private::dump()
443{
444 size_t l = data.size();
445 size_t i =0;
446 DBG("==== compiled token stream for pattern '%s' ===\n",pattern.c_str());
447 DBG("captureCount=%zu\n",captureCount);
448 while (i<l)
449 {
450 DBG("[%s:%04x]\n",data[i].kindStr(),data[i].value());
451 if (data[i].kind()==PToken::Kind::CharClass || data[i].kind()==PToken::Kind::NegCharClass)
452 {
453 uint16_t num = data[i].value();
454 while (num>0 && i<l)
455 {
456 i++;
457 if (data[i].isRange()) // from-to range
458 {
459 DBG("[%04x(%c)-%04x(%c)]\n",data[i].from(),data[i].from(),data[i].to(),data[i].to());
460 }
461 else // special character like \n or \s
462 {
463 DBG("[%s:%04x]\n",data[i].kindStr(),data[i].value());
464 }
465 num--;
466 }
467 }
468 i++;
469 }
470}
471#endif
472
473/** Internal matching routine.
474 * @param tokenPos Offset into the token stream.
475 * @param tokenLen The length of the token stream.
476 * @param str The input string to match against.
477 * @param match The object used to store the matching results.
478 * @param pos The position in the input string to start with matching
479 * @param level Recursion level (used for debugging)
480 */
481bool Ex::Private::matchAt(size_t tokenPos,size_t tokenLen,std::string_view str,Match &match,const size_t pos,int level) const
482{
483 DBG("%d:matchAt(tokenPos=%zu, str='%s', pos=%zu)\n",level,tokenPos,pos<str.length() ? str.substr(pos).c_str() : "",pos);
484 auto isStartIdChar = [](char c) { return isalpha(c) || c=='_'; };
485 auto isIdChar = [](char c) { return isalnum(c) || c=='_'; };
486 auto matchCharClass = [this,isStartIdChar,isIdChar](size_t tp,char c) -> bool
487 {
488 PToken tok = data[tp];
489 bool negate = tok.kind()==PToken::Kind::NegCharClass;
490 uint16_t numFields = tok.value();
491 bool found = false;
492 for (uint16_t i=0;i<numFields;i++)
493 {
494 tok = data[++tp];
495 // first check for built-in ranges
496 if ((tok.kind()==PToken::Kind::Alpha && isStartIdChar(c)) ||
497 (tok.kind()==PToken::Kind::AlphaNum && isIdChar(c)) ||
498 (tok.kind()==PToken::Kind::WhiteSpace && isspace(c)) ||
499 (tok.kind()==PToken::Kind::Digit && isdigit(c))
500 )
501 {
502 found=true;
503 break;
504 }
505 else // user specified range
506 {
507 uint16_t v = static_cast<uint16_t>(c);
508 if (tok.from()<=v && v<=tok.to())
509 {
510 found=true;
511 break;
512 }
513 }
514 }
515 DBG("matchCharClass(tp=%zu,c=%c (x%02x))=%d\n",tp,c,c,negate?!found:found);
516 return negate ? !found : found;
517 };
518 size_t index = pos;
519 enum SequenceType { Star, Optional, OptionalRange };
520 auto processSequence = [this,&tokenPos,&tokenLen,&index,&str,&matchCharClass,
521 &isStartIdChar,&isIdChar,&match,&level,&pos](SequenceType type) -> bool
522 {
523 size_t startIndex = index;
524 size_t len = str.length();
525 PToken tok = data[++tokenPos];
526
527 // Special handling for an optional capture group: (...)?
528 if (type==OptionalRange && tok.kind()==PToken::Kind::BeginCapture)
529 {
530 size_t groupId = tok.value();
531 size_t innerStart = tokenPos + 1;
532
533 // Find matching EndCapture, accounting for nesting depth
534 size_t tp = innerStart;
535 int depth = 1;
536 while (tp<tokenLen && depth>0)
537 {
538 if (data[tp].kind()==PToken::Kind::BeginCapture) depth++;
539 else if (data[tp].kind()==PToken::Kind::EndCapture) depth--;
540 tp++;
541 }
542 if (depth!=0) return false; // malformed, unmatched ')'
543 size_t endCapturePos = tp - 1; // position of EndCapture
544 size_t afterSeqPos = endCapturePos + 2; // skip EndCapture and End marker
545
546 // Try with the group present
547 Match tmp;
548 tmp.init(str, /*captureCount*/ captureCount);
549 bool innerOk = matchAt(innerStart,endCapturePos,str,tmp,index,level+1);
550 if (innerOk)
551 {
552 size_t capLen = tmp.length();
553
554 // Copy nested captures from tmp (they may exist inside the group)
555 for (size_t gid=1; gid<tmp.size(); gid++)
556 {
557 size_t sp = tmp[gid].position();
558 size_t sl = tmp[gid].length();
559 if (sp!=std::string::npos && sl!=std::string::npos)
560 {
561 match.startCapture(gid,sp);
562 match.endCapture(gid,sp+sl);
563 }
564 }
565 // Set the outer group's capture
566 match.startCapture(groupId,index);
567 match.endCapture(groupId,index+capLen);
568
569 bool ok = matchAt(afterSeqPos,tokenLen,str,match,index+capLen,level+1);
570 if (ok)
571 {
572 match.setMatch(pos,(index+capLen)-pos+match.length());
573 return true;
574 }
575 }
576
577 // Try with the group absent (empty capture)
578 match.startCapture(groupId,index);
579 match.endCapture(groupId,index); // zero-length
580
581 bool ok2 = matchAt(afterSeqPos,tokenLen,str,match,index,level+1);
582 if (ok2)
583 {
584 match.setMatch(pos,index-pos+match.length());
585 return true;
586 }
587 return false;
588 }
589
590 if (tok.kind()==PToken::Kind::Character) // 'x*' or 'x?'
591 {
592 char c_tok = tok.asciiValue();
593 while (index<len && str[index]==c_tok) { index++; if (type==Optional) break; }
594 tokenPos++;
595 }
596 else if (tok.isCharClass()) // '[a-f0-4]*' or '[...]?' -> eat matching characters
597 {
598 while (index<len && matchCharClass(tokenPos,str[index])) { index++; if (type==Optional) break; }
599 tokenPos+=tok.value()+1; // skip over character ranges + end token
600 }
601 else if (tok.kind()==PToken::Kind::Alpha) // '\a*' or '\a?' -> eat start id characters
602 {
603 while (index<len && isStartIdChar(str[index])) { index++; if (type==Optional) break; }
604 tokenPos++;
605 }
606 else if (tok.kind()==PToken::Kind::AlphaNum) // '\w*' or '\w?' -> eat id characters
607 {
608 while (index<len && isIdChar(str[index])) { index++; if (type==Optional) break; }
609 tokenPos++;
610 }
611 else if (tok.kind()==PToken::Kind::WhiteSpace) // '\s*' or '\s?' -> eat spaces
612 {
613 while (index<len && isspace(str[index])) { index++; if (type==Optional) break; }
614 tokenPos++;
615 }
616 else if (tok.kind()==PToken::Kind::Digit) // '\d*' or '\d?' -> eat digits
617 {
618 while (index<len && isdigit(str[index])) { index++; if (type==Optional) break; }
619 tokenPos++;
620 }
621 else if (tok.kind()==PToken::Kind::Any) // '.*' or '.?' -> eat all
622 {
623 if (type==Optional) index++; else index = str.length();
624 tokenPos++;
625 }
626 else if (type==OptionalRange && tok.kind()==PToken::Kind::BeginCapture)
627 {
628 size_t tokenStart = ++tokenPos;
629 while (tokenPos<tokenLen && data[tokenPos].kind()!=PToken::Kind::EndCapture) { tokenPos++; }
630 Match rangeMatch;
631 rangeMatch.init(str,0);
632 bool found = matchAt(tokenStart,tokenPos,str,rangeMatch,index,level+1);
633 if (found)
634 {
635 index+=rangeMatch.length(); // (abc)? matches -> eat all
636 }
637 tokenPos++; // skip over EndCapture
638 }
639 tokenPos++; // skip over end marker
640 while (index>=startIndex)
641 {
642 // pattern 'x*xy' should match 'xy' and 'xxxxy'
643 bool found = matchAt(tokenPos,tokenLen,str,match,index,level+1);
644 if (found)
645 {
646 match.setMatch(pos,index-pos+match.length());
647 return true;
648 }
649 if (index==0) break;
650 index--;
651 }
652 return false;
653 };
654
655 while (tokenPos<tokenLen)
656 {
657 PToken tok = data[tokenPos];
658 DBG("loop tokenPos=%zu token=%s\n",tokenPos,tok.kindStr());
659 if (tok.kind()==PToken::Kind::Character) // match literal character
660 {
661 char c_tok = tok.asciiValue();
662 if (index>=str.length() || str[index]!=c_tok) return false; // end of string, or non matching char
663 index++;
664 tokenPos++;
665 }
666 else if (tok.isCharClass())
667 {
668 if (index>=str.length() || !matchCharClass(tokenPos,str[index])) return false;
669 index++;
670 tokenPos+=tok.value()+1; // skip over character ranges + end token
671 }
672 else
673 {
674 switch (tok.kind())
675 {
677 if (index>=str.length() || !isStartIdChar(str[index])) return false;
678 index++;
679 break;
681 if (index>=str.length() || !isIdChar(str[index])) return false;
682 index++;
683 break;
685 if (index>=str.length() || !isspace(str[index])) return false;
686 index++;
687 break;
689 if (index>=str.length() || !isdigit(str[index])) return false;
690 index++;
691 break;
693 if (index!=pos) return false;
694 break;
696 if (index<str.length()) return false;
697 break;
699 DBG("BeginOfWord: index=%zu isIdChar(%c)=%d prev.isIdChar(%c)=%d\n",
700 index,str[index],isIdChar(str[index]),
701 index>0?str[index]-1:0,
702 index>0?isIdChar(str[index-1]):-1);
703 if (index>=str.length() ||
704 !isIdChar(str[index]) ||
705 (index>0 && isIdChar(str[index-1]))) return false;
706 break;
708 DBG("EndOfWord: index=%zu pos=%zu idIdChar(%c)=%d prev.isIsChar(%c)=%d\n",
709 index,pos,str[index],isIdChar(str[index]),
710 index==0 ? 0 : str[index-1],
711 index==0 ? -1 : isIdChar(str[index-1]));
712 if (index<str.length() &&
713 (isIdChar(str[index]) || index==0 || !isIdChar(str[index-1]))) return false;
714 break;
716 DBG("BeginCapture(%zu) gid=%u\n",index,tok.value());
717 match.startCapture(tok.value(),index);
718 break;
720 DBG("EndCapture(%zu) gid=%u\n",index,tok.value());
721 match.endCapture(tok.value(),index);
722 break;
724 if (index>=str.length()) return false;
725 index++;
726 break;
728 return processSequence(Star);
730 if (tokenPos<tokenLen-1 && data[tokenPos+1].kind()==PToken::Kind::BeginCapture)
731 {
732 return processSequence(OptionalRange); // (...)?
733 }
734 else
735 {
736 return processSequence(Optional); // x?
737 }
738 default:
739 return false;
740 }
741 tokenPos++;
742 }
743 }
744 match.setMatch(pos,index-pos);
745 return true;
746}
747
748static std::string wildcard2regex(std::string_view pattern)
749{
750 std::string result="^"; // match start of input
751 result.reserve(pattern.length());
752 for (size_t i=0;i<pattern.length();i++)
753 {
754 char c=pattern[i];
755 switch(c)
756 {
757 case '*':
758 result+=".*";
759 break; // '*' => '.*'
760 case '?':
761 result+='.';
762 break; // '?' => '.'
763 case '.':
764 case '+':
765 case '\\':
766 case '$':
767 case '^':
768 case '(':
769 case ')':
770 result+='\\'; result+=c; // escape
771 break;
772 case '[':
773 if (i<pattern.length()-1 && pattern[i+1]=='^') // don't escape ^ after [
774 {
775 result+="[^";
776 i++;
777 }
778 else
779 {
780 result+=c;
781 }
782 break;
783 default: // just copy
784 result+=c;
785 break;
786 }
787 }
788 result+='$'; // match end of input
789 return result;
790}
791
792
793Ex::Ex(std::string_view pattern, Mode mode)
794 : p(std::make_unique<Private>(mode==Mode::RegEx ? pattern : wildcard2regex(pattern)))
795{
796 p->compile();
797#if ENABLE_DEBUG
798 p->dump();
799 assert(!p->error);
800#endif
801}
802
803Ex::~Ex() = default;
804
805bool Ex::match(std::string_view str,Match &match,size_t pos) const
806{
807 bool found=false;
808 if (p->data.size()==0 || p->error) return found;
809 match.init(str,p->captureCount);
810
811 PToken tok = p->data[0];
812 if (tok.kind()==PToken::Kind::BeginOfLine) // only test match at the given position
813 {
814 found = p->matchAt(0,p->data.size(),str,match,pos,0);
815 }
816 else
817 {
818 if (tok.kind()==PToken::Kind::Character) // search for the start character
819 {
820 size_t index = str.find(tok.asciiValue(),pos);
821 if (index==std::string::npos)
822 {
823 DBG("Ex::match(str='%s',pos=%zu)=false (no start char '%c')\n",std::string(str).c_str(),pos,tok.asciiValue());
824 return false;
825 }
826 DBG("pos=%zu str='%s' char='%c' index=%zu\n",index,std::string(str).c_str(),tok.asciiValue(),index);
827 pos=index;
828 }
829 while (pos<str.length()) // search for a match starting at pos
830 {
831 found = p->matchAt(0,p->data.size(),str,match,pos,0);
832 if (found) break;
833 pos++;
834 }
835 }
836 DBG("Ex::match(str='%s',pos=%zu)=%d\n",std::string(str).c_str(),pos,found);
837 return found;
838}
839
840bool Ex::isValid() const
841{
842 return !p->pattern.empty() && !p->error;
843}
844
845//----------------------------------------------------------------------------------------
846
847bool search(std::string_view str,Match &match,const Ex &re,size_t pos)
848{
849 return re.match(str,match,pos);
850}
851
852bool search(std::string_view str,const Ex &re,size_t pos)
853{
854 Match match;
855 return re.match(str,match,pos);
856}
857
858bool match(std::string_view str,Match &match,const Ex &re)
859{
860 return re.match(str,match,0) && match.position()==0 && match.length()==str.length();
861}
862
863bool match(std::string_view str,const Ex &re)
864{
865 Match match;
866 return re.match(str,match,0) && match.position()==0 && match.length()==str.length();
867}
868
869std::string replace(std::string_view str,const Ex &re,std::string_view replacement)
870{
871 std::string result;
872 Match match;
873 size_t p=0;
874 while (re.match(str,match,p))
875 {
876 size_t i=match.position();
877 size_t l=match.length();
878 if (i>p) result+=str.substr(p,i-p);
879 result+=replacement;
880 p=i+l;
881 }
882 if (p<str.length()) result+=str.substr(p);
883 return result;
884}
885
886}
Private members of a regular expression.
Definition regex.cpp:170
size_t captureCount
Number of capture groups in the pattern (excluding the whole match).
Definition regex.cpp:194
bool error
Flag indicating the expression was successfully compiled.
Definition regex.cpp:185
void compile()
Compiles a regular expression passed as a string into a stream of tokens that can be used for efficie...
Definition regex.cpp:200
std::string pattern
The pattern string as passed by the user.
Definition regex.cpp:191
Private(std::string_view pat)
Creates the private part.
Definition regex.cpp:173
bool matchAt(size_t tokenPos, size_t tokenLen, std::string_view str, Match &match, size_t pos, int level) const
Internal matching routine.
Definition regex.cpp:481
std::vector< PToken > data
The token stream representing the compiled regular expression.
Definition regex.cpp:188
Class representing a regular expression.
Definition regex.h:39
~Ex()
Destroys the regular expression object.
std::unique_ptr< Private > p
Definition regex.h:112
bool match(std::string_view str, Match &match, size_t pos=0) const
Check if a given string matches this regular expression.
Definition regex.cpp:805
Ex(std::string_view pattern, Mode mode=Mode::RegEx)
Creates a regular expression object given the pattern as a string.
Definition regex.cpp:793
Mode
Matching algorithm.
Definition regex.h:43
bool isValid() const
Definition regex.cpp:840
Object representing the matching results.
Definition regex.h:151
void init(std::string_view str, size_t captureCount)
Definition regex.h:196
size_t size() const
Returns the number of sub matches available in this match.
Definition regex.h:182
size_t position() const
Returns the position of the match or std::string::npos if no position is set.
Definition regex.h:157
size_t length() const
Returns the position of the match or std::string::npos if no length is set.
Definition regex.h:160
Class representing a token in the compiled regular expression token stream.
Definition regex.cpp:59
uint16_t to() const
Returns the 'to' part of the character range.
Definition regex.cpp:150
char asciiValue() const
Returns the value for this token as a ASCII character.
Definition regex.cpp:156
PToken(Kind k)
Creates a token of the given kind k.
Definition regex.cpp:127
PToken(char c)
Create a token for an ASCII character.
Definition regex.cpp:130
bool isRange() const
Returns true iff this token represents a range of characters.
Definition regex.cpp:159
Kind kind() const
Returns the kind of the token.
Definition regex.cpp:144
PToken()
Creates a token of kind 'End'.
Definition regex.cpp:124
uint16_t from() const
Returns the 'from' part of the character range.
Definition regex.cpp:147
const char * kindStr() const
returns a string representation of the tokens kind (useful for debugging).
Definition regex.cpp:92
Kind
The kind of token.
Definition regex.cpp:71
uint32_t m_rep
Definition regex.cpp:165
void setValue(uint16_t value)
Sets the value for a token.
Definition regex.cpp:141
uint16_t value() const
Returns the value for this token.
Definition regex.cpp:153
PToken(uint16_t v)
Create a token for a byte of an UTF-8 character.
Definition regex.cpp:134
bool isCharClass() const
Returns true iff this token is a positive or negative character class.
Definition regex.cpp:162
PToken(uint16_t from, uint16_t to)
Create a token representing a range from one character from to another character to.
Definition regex.cpp:138
#define DBG(x)
Definition dotrunner.cpp:70
Namespace for the regular expression functions.
Definition regex.cpp:31
static bool isalpha(char c)
Definition regex.cpp:38
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:847
static std::string wildcard2regex(std::string_view pattern)
Definition regex.cpp:748
std::string replace(std::string_view str, const Ex &re, std::string_view replacement)
Searching in a given input string for parts that match regular expression re and replaces those parts...
Definition regex.cpp:869
bool match(std::string_view str, Match &match, const Ex &re)
Matches a given string str for a match against regular expression re.
Definition regex.cpp:858
static bool isspace(char c)
Definition regex.cpp:33
static bool isalnum(char c)
Definition regex.cpp:48
static bool isdigit(char c)
Definition regex.cpp:43