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