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 169 of file regex.cpp.

Constructor & Destructor Documentation

◆ Private()

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

Creates the private part.

Definition at line 173 of file regex.cpp.

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

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 200 of file regex.cpp.

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}
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
PToken()
Creates a token of kind 'End'.
Definition regex.cpp:124

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::PToken(), 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 481 of file regex.cpp.

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

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 194 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 188 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 185 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 191 of file regex.cpp.

Referenced by compile(), and Private().


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