Doxygen
Loading...
Searching...
No Matches
reg::Ex::Private Class Reference

Private members of a regular expression. More...

Public Member Functions

 Private (std::string_view pat)
 Creates the private part.
void compile ()
 Compiles a regular expression passed as a string into a stream of tokens that can be used for efficient searching.
bool matchAt (size_t tokenPos, size_t tokenLen, std::string_view str, Match &match, size_t pos, int level) const
 Internal matching routine.

Public Attributes

bool error = false
 Flag indicating the expression was successfully compiled.
std::vector< PTokendata
 The token stream representing the compiled regular expression.
std::string pattern
 The pattern string as passed by the user.
size_t captureCount = 0
 Number of capture groups in the pattern (excluding the whole match).

Detailed Description

Private members of a regular expression.

Definition at line 172 of file regex.cpp.

Constructor & Destructor Documentation

◆ Private()

reg::Ex::Private::Private ( std::string_view pat)
inline

Creates the private part.

Definition at line 176 of file regex.cpp.

176 : pattern(pat)
177 {
178 data.reserve(100);
179 }
std::string pattern
The pattern string as passed by the user.
Definition regex.cpp:194
std::vector< PToken > data
The token stream representing the compiled regular expression.
Definition regex.cpp:191

References data, and pattern.

Member Function Documentation

◆ compile()

void reg::Ex::Private::compile ( )

Compiles a regular expression passed as a string into a stream of tokens that can be used for efficient searching.

Definition at line 203 of file regex.cpp.

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;
290 addToken(PToken(PToken::Kind::BeginCapture));
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();
306 addToken(PToken(PToken::Kind::EndCapture));
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 {
321 addToken(PToken(PToken::Kind::NegCharClass));
322 if (*ps==0) { error=true; return; }
323 tok = getNextCharacter();
324 ps++;
325 }
326 else
327 {
328 addToken(PToken(PToken::Kind::CharClass));
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,
419 c=='?' ? PToken(PToken::Kind::Optional) : PToken(PToken::Kind::Star));
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}
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

References reg::PToken::Alpha, reg::PToken::AlphaNum, reg::PToken::Any, reg::PToken::asciiValue(), reg::PToken::BeginCapture, reg::PToken::BeginOfLine, reg::PToken::BeginOfWord, captureCount, reg::PToken::Character, reg::PToken::CharClass, data, reg::PToken::Digit, reg::PToken::End, reg::PToken::EndCapture, reg::PToken::EndOfLine, reg::PToken::EndOfWord, error, reg::PToken::kind(), reg::PToken::NegCharClass, reg::PToken::Optional, pattern, reg::PToken::Star, reg::PToken::value(), and reg::PToken::WhiteSpace.

◆ matchAt()

bool reg::Ex::Private::matchAt ( size_t tokenPos,
size_t tokenLen,
std::string_view str,
Match & match,
size_t pos,
int level ) const

Internal matching routine.

Parameters
tokenPosOffset into the token stream.
tokenLenThe length of the token stream.
strThe input string to match against.
matchThe object used to store the matching results.
posThe position in the input string to start with matching
levelRecursion level (used for debugging)

Definition at line 484 of file regex.cpp.

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}
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
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
#define DBG(x)
Definition dotrunner.cpp:74
static bool isalpha(char c)
Definition regex.cpp:41
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

References reg::PToken::Alpha, reg::PToken::AlphaNum, reg::PToken::Any, reg::PToken::asciiValue(), reg::PToken::BeginCapture, reg::PToken::BeginOfLine, reg::PToken::BeginOfWord, captureCount, reg::PToken::Character, data, DBG, reg::PToken::Digit, reg::PToken::EndCapture, reg::PToken::EndOfLine, reg::PToken::EndOfWord, reg::PToken::from(), reg::Match::init(), reg::isalnum(), reg::isalpha(), reg::PToken::isCharClass(), reg::isdigit(), reg::isspace(), reg::PToken::kind(), reg::PToken::kindStr(), reg::Match::length(), reg::Ex::match(), matchAt(), reg::PToken::NegCharClass, reg::PToken::Optional, reg::Match::position(), reg::Match::size(), reg::PToken::Star, reg::PToken::to(), reg::PToken::value(), and reg::PToken::WhiteSpace.

Referenced by matchAt().

Member Data Documentation

◆ captureCount

size_t reg::Ex::Private::captureCount = 0

Number of capture groups in the pattern (excluding the whole match).

Definition at line 197 of file regex.cpp.

Referenced by compile(), and matchAt().

◆ data

std::vector<PToken> reg::Ex::Private::data

The token stream representing the compiled regular expression.

Definition at line 191 of file regex.cpp.

Referenced by compile(), matchAt(), and Private().

◆ error

bool reg::Ex::Private::error = false

Flag indicating the expression was successfully compiled.

Definition at line 188 of file regex.cpp.

Referenced by compile().

◆ pattern

std::string reg::Ex::Private::pattern

The pattern string as passed by the user.

Definition at line 194 of file regex.cpp.

Referenced by compile(), and Private().


The documentation for this class was generated from the following file: