रेगेक्स इंजन के अंदरूनी हिस्से अब लाइब्रेरी के रूप में
(blog.burntsushi.net)- Rust
regexcrate ने कई वर्षों के rewrite के बाद अपने internal engine के बड़े हिस्से को अलग version वालेregex-automatacrate API के रूप में public किया, औरregex 1.9में यह transition पूरा हुआ - rewrite की शुरुआत search strategy के मेल-जोल की कठिनाई, internal engine-वार testing की मुश्किल,
RegexSetसे अधिक granular multi-pattern API की मांग, और fully compiled DFA को साझा करने की ज़रूरत से हुई regex-automataregex processing कोAst → Hir → literal extraction/Prefilter → Thompson NFA → PikeVM·BoundedBacktracker·one-pass DFA·DFA·lazy DFA → meta engineflow में संगठित करता है- performance इस तरह हासिल की जाती है कि जहाँ संभव हो पहले literal search और DFA परिवार के engine इस्तेमाल किए जाते हैं, और capture group या exceptional case में
PikeVM·bounded backtracker·one-pass DFA से पूरक किया जाता है - public API और reusable abstraction ने testing और experimentation को आसान बनाया, लेकिन code size, binary size और compile time बढ़े; fully compiled DFA को opt-in रखा गया और
regex-liteको हल्के विकल्प के रूप में जोड़ा गया
regex rewrite और regex-automata का public release
- Rust का
regexcrate internal composition को बेहतर बनाने, optimization को और आसानी से जोड़ने, और correctness बनाए रखने के लिए कई वर्षों तक rewrite किया गया - इस प्रक्रिया में
regex-automataबना, जोregexcrate के internal implementation के बड़े हिस्से को अलग API के रूप में expose करता है regex-automataको इस रूप में पेश किया गया कि regex library के अंदरूनी हिस्सों को इस स्तर तक अलग version वाली library के रूप में public करने का यह पहला उदाहरण हैregex 1.95 जुलाई 2023 को release हुआ और इसी के साथ यह rewrite पूरा हुआ- यह Rust programmers और finite automata आधारित regex engine implementation में रुचि रखने वालों के लिए है, और regex के अनुभव को पूर्वधारणा मानता है
rewrite से पहले की समस्याएँ
- मौजूदा
regexcrate ने RE2 परंपरा का पालन करते हुए internal रूप से कई search strategies का उपयोग किया, लेकिन ये strategies समय के साथ organic तरीके से जुड़ती गईं, जिससे इनका संयोजन कठिन हो गयाPikeVMको पहली strategy के रूप में डिज़ाइन किया गया था, इसलिए lazy DFA के साथ संयोजन में ज़रूरी partial-slice search start/stop handling की कमी थी- यह समझना कठिन था कि किस regex पर कौन-सी strategy इस्तेमाल होगी
- कई
matchexpressions एक ही logic को बार-बार implement करते थे, जिससे synchronization बिगड़ना आसान था - जिन regex में केवल Aho-Corasick ही काफ़ी होता, उनमें भी इस्तेमाल न होने वाला Thompson NFA अनावश्यक रूप से बन सकता था
- internal engine testing भी कठिन थी
- public API एक ही regex engine जैसा दिखता है, लेकिन अंदर कई strategies हैं, और उन्हें एक ही input पर समान व्यवहार करना चाहिए
regex 1.9से पहले internal strategies public API नहीं थीं, इसलिए हर engine को स्वतंत्र रूप से test करना मुश्किल था- मौजूदा tests का ढाँचा internal API exposure, undocumented
Fromimplementations, macros, और engine-specific test targets पर निर्भर लगभग hack जैसा था
- कई niche API अनुरोध भी मौजूदा API surface में जोड़ना असहज था
RegexSetकेवल यह बताता है कि कौन-सा pattern haystack में कहीं match हुआ, लेकिन match offset या capture group offset नहीं देता- pattern में
^जोड़े बिना anchored search चलाने की क्षमता की ज़रूरत थी - search के दौरान internal synchronization के बिना mutable scratch space को सीधे पास करने की सुविधा माँगी गई
- stream या rope जैसे non-contiguous haystack पर regex चलाने की क्षमता की भी माँग थी
- अगर अंदरूनी हिस्सों को अलग version crate के रूप में public किया जाए, तो general-purpose
regexAPI को जटिल बनाए बिना expert API को तेज़ breaking change cycle में प्रयोग किया जा सकता है
fully compiled DFA ने abstraction boundary कैसे तय की
regex-automataकी शुरुआती प्रेरणा एक fully compiled DFA बनाना, उसे serialize करना, और फिर zero-copy deserialization के साथ search करने वाला minimal runtime देना था- शुरुआती
regex-automataका उपयोगbstrके Unicode algorithm implementation के लिए DFA बनाने में हुआ - DFA बनाते समय
regexcrate जैसे NFA data structure और compiler की आवश्यकता पड़ी, और यह code जटिल होने पर उसे साझा करने की ज़रूरत बढ़ी - शुरुआत में
regex-nfaजैसे अलग crate पर विचार हुआ, लेकिन determinization process सहित और भी बहुत-सा coderegexऔरregex-automataके बीच साझा किया जा सकता था - abstraction boundary “NFA” से ज़्यादा regex engine के करीब निकली, और अंततः
regex-automataको कई engines के संग्रह के रूप में फिर से परिभाषित किया गया - long-term plan यह था कि सभी regex engines को
regex-automataमें रखा जाए औरregexcrate को उसके ऊपर एक पतला wrapper बनाया जाए
regex-cli से internal structure देखना
regex-cliएक program है जिसेregexcrate repository में maintain किया जाता है, और यहregex-syntax,regex-automata,regexकी विभिन्न APIs तक command-line access देता है- इसे निम्न command से install किया जा सकता है
cargo install regex-cli
regex-cli debugAST, HIR, literal, Thompson NFA, one-pass DFA, dense DFA, sparse DFA आदि को output कर सकता है- Unicode enabled
.regex, UTF-8 scalar value को handle करने के लिए कहीं अधिक जटिल Thompson NFA बनाता है, जबकि Unicode disabled(?-u:.)अधिक सरल NFA बनाता है regex-cli findad-hoc search चला सकता है, और meta engine के साथ multi-pattern search तथा capture group output भी दे सकता है
regex processing data flow
Regex::newको दिया गया pattern string पहलेAstमें parse होता हैAstकोHirमें convert किया जाता हैHir,Astकी तुलना में कम detailed होता है, और conversion के दौरान Unicode case folding तथा Unicode character class references expand होते हैं
Hirसे दो चीज़ें बनती हैं- search optimization में इस्तेमाल होने वाली literal sequence
- Thompson
NFA
- NFA कई engines बनाने की नींव बनता है
PikeVM: parse हो सकने वाले सभी regex को संभालता है और capture group offset report करता हैBoundedBacktracker: bounded backtracking के साथ capture group offset report करता है- one-pass DFA: सीमित regex में capture group offset तेज़ी से report करता है
- dense DFA: बहुत तेज़ है, लेकिन केवल full match की शुरुआत/अंत report करता है; इसका construction worst-case
O(2^m)time और space ले सकता है - lazy DFA: search के दौरान NFA से DFA बनाता है, आम तौर पर full DFA जितना तेज़ होता है और full DFA की exponential construction cost से बचता है
- इन engines और
Prefilterको मिलाकर एक meta regex engine बनाया जाता है, औरregexcrate इस meta engine का एक पतला wrapper है
लिटरल ऑप्टिमाइज़ेशन
- लिटरल extraction,
regexके अंदर की एक मुख्य optimization है- उदाहरण के लिए,
(foo|bar|quux)(\s+\w+)के सभी match,foo,bar,quuxमें से किसी एक से शुरू होते हैं
- उदाहरण के लिए,
- लिटरल महत्वपूर्ण इसलिए हैं क्योंकि एक या कुछ strings के लिए search algorithms बहुत तेज़ होते हैं
- vector instructions के साथ haystack के कई bytes को एक साथ प्रोसेस किया जा सकता है
- सामान्य regex matching algorithms को इसी तरह लगातार accelerate करना कठिन है
- अच्छी तरह optimized substring search, अक्सर सामान्य regex engine से कम-से-कम एक order of magnitude तक तेज़ रही है
- लिटरल extraction एक heuristic है
- candidate match की false positive rate को कम करना चाहिए
- prefilter का पूरे latency पर असर भी कम होना चाहिए
- दोनों शर्तें haystack पर निर्भर हैं, लेकिन search से पहले haystack का analysis करने पर कुल search time और खराब हो सकता है
- लिटरल sequence, set नहीं बल्कि ordered sequence है
regexcrate, Perl-like leftmost-first semantics का पालन करता है, इसलिए|पर commutativity लागू नहीं होतीsam|samwiseमें केवलsamनिकाला जा सकता है, लेकिनsamwise|samमें दोनों पर विचार किया जाता है
- single string search के लिए
memchrcrate काmemmemmodule इस्तेमाल होता है- मुख्य algorithm Two-Way है, जिसका worst-case समय
O(n)और constant space होता है - छोटे needle और haystack के लिए Rabin-Karp इस्तेमाल होता है
x86_64पर generic SIMD variants का उपयोग किया जाता है
- मुख्य algorithm Two-Way है, जिसका worst-case समय
- multiple string search के लिए Hyperscan से port किया गया Teddy मुख्य algorithm के रूप में इस्तेमाल होता है, और कुछ मामलों में Aho-Corasick भी उपयोग होता है
Thompson NFA और optimization
regexcrate के अंदर का केंद्रीय data structure Thompson NFA है- Thompson’s construction, regex की structured representation से
O(m)समय में NFA बनाता है, जहाँm, counted repetition expansion के बाद regex के आकार के अनुपात में होता है - NFA को सीधे regex engine के रूप में इस्तेमाल किया जा सकता है, और DFA जैसे दूसरे types में बदलकर अन्य engines की नींव के रूप में भी उपयोग किया जा सकता है
- नए NFA compiler की मुख्य optimization, epsilon transition को कम करने पर केंद्रित है
- Thompson NFA का construction time अच्छा होता है, लेकिन यह epsilon transition का बहुत उपयोग करता है
- epsilon closure की गणना, search या DFA construction के दौरान बार-बार लागत पैदा कर सकती है
sparsestate optimization, कई range transitions को एक state में व्यक्त करके पहले के कईSplitinstructions को कम करती है[A-Za-z0-9]जैसी regex में अनावश्यक epsilon transition हटाती है- मौजूदा representation में indirection की वजह से cache impact और heap memory बढ़ने की संभावना है
- minimal UTF-8 automata optimization, बड़े Unicode class में NFA के आकार को काफी कम करती है
- पुराने byte-oriented NFA में
\w, 3,564 states बना सकता था - नया compiler, Daciuk’s algorithm का उपयोग करके बहुत कम states और zero epsilon transition वाली संरचना बनाता है
- compile time cost के कारण reverse NFA का shrink, डिफ़ॉल्ट रूप से disabled है
- पुराने byte-oriented NFA में
- literal trie optimization,
zap|z|zapper,abc|xyzजैसी literal alternation में common structure को trie के रूप में compile करके epsilon transition कम करती है- leftmost-first semantics को बनाए रखने के लिए match दिखने वाले हर बिंदु पर transition chunks को विभाजित किया जाता है
- भविष्य के NFA कार्य के रूप में Glushkov NFA और single contiguous allocation storage पर विचार किया जा रहा है
- Glushkov NFA में epsilon transition नहीं होते, लेकिन compile time complexity अधिक खराब है
- contiguous allocation, cache friendliness और zero-copy serialization·deserialization की संभावना दे सकता है, लेकिन code complexity और
unsafeके उपयोग की संभावना भी है
अलग-अलग regex engines
regex-automataके engines, मिलती-जुलती API साझा करते हैंInput: haystack, search range, anchored है या नहीं, early stop है या नहीं को सेट करता हैMatch: matched byte span औरPatternIDरखता हैMatchError: ऐसी error को दिखाता है जिसमें search result निर्धारित नहीं किया जा सकता
-
PikeVM
regex-syntaxजो पूरी functionality parse कर सकता है, उसे support करता है, और किसी भी लंबाई के haystack पर काम करता है- capture group positions को track करता है, और active states को lock-step में calculate करके worst-case
O(m * n)समय की guarantee देता है - कई states और capture group positions को track करना पड़ता है, इसलिए performance इसकी मुख्य कमजोरी है
regex-automataengines में इसकी एक खास विशेषता है कि यह search के दौरान error return नहीं करता
-
BoundedBacktracker
- Thompson NFA पर backtracking का उपयोग करता है, लेकिन पहले किए गए काम को दोबारा trace न करना पड़े इसके लिए अतिरिक्त state का उपयोग करता है
- worst-case
O(m * n)समय की guarantee देता है, लेकिनO(m * n)space इस्तेमाल करता है - मोटे प्रयोगों में यह आम तौर पर
PikeVMसे लगभग 2 गुना तेज़ है - अगर haystack की लंबाई और regex का आकार configured visited capacity से आगे निकल जाए तो यह fail हो सकता है
-
one-pass DFA
- सीमित one-pass NFA में capture group offsets को बहुत तेज़ी से report करता है
- इसे capture group report करने का सबसे तेज़ तरीका माना जाता है
- केवल anchored search support करता है, और कई regex one-pass नहीं होते
- Unicode mode में byte level transition overlap की वजह से जो regex one-pass नहीं थे, वे Unicode बंद करने पर one-pass बन सकते हैं
-
fully compiled DFA
- पूरे match के अंत और शुरुआत को खोजने के लिए forward DFA और reverse DFA, इन दो से मिलकर बना होता है
- construction worst-case
O(2^m)समय और space लेता है, और dense DFA बहुत memory इस्तेमाल करता है - full DFA engine,
regexcrate में डिफ़ॉल्ट रूप से disabled है औरperf-dfa-fullfeature से opt-in करना पड़ता है - mutable
Cacheके बिना काम करता है, और raw bytes के रूप में serialize किया जा सकता है ताकि केवलcoreवाले environment में भी search runtime इस्तेमाल हो सके
-
hybrid NFA/DFA, lazy DFA
- full DFA के समान है, लेकिन transition table को search के दौरान बनाता है
- अगर cache में पहले से calculated transition हो तो उसे reuse करता है, और नहीं होने पर NFA powerset construction से केवल वही transition calculate करता है
- worst-case search time
O(m * n)है, और space construction के समय तय की गई cache capacity से सीमित रहता है - सामान्य मामलों में ज़्यादातर states और transitions cache हो जाते हैं, इसलिए औसतन यह
O(n)जैसा काम करता है, और वास्तव में कई regex पर full DFA जैसी search performance दिखाता है - अगर cache बार-बार भरकर अक्षम हो जाए तो यह error return करता है, और meta engine में आम तौर पर किसी दूसरे engine से retry किया जाता है
meta regex engine की भूमिका
meta regex engineकई इंजनों को एक में जोड़ता है और caller को infallible API देने की कोशिश करता है- caller को हर search के लिए
Cacheखुद बनाकर पास करने की ज़रूरत नहीं होती- meta engine अंदरूनी thread-safe cache pool को मैनेज करता है
- synchronization cost से बचना हो तो explicit
Cacheपास करने वाला lower-level API भी दिया जाता है
regex::Regex,regex::RegexSet,regex::bytes::Regex,regex::bytes::RegexSetसभी meta engine के thin wrapper हैं- meta engine की आंतरिक strategy मोटे तौर पर इस तरह है
- अगर regular expression engine के बिना सिर्फ single या multiple substring search से काम हो सकता है, तो NFA बनाना भी टाल दिया जाता है
- जहाँ संभव हो, prefix literal sequence निकालकर उसे
Prefilterकी तरह इस्तेमाल किया जाता है - अगर prefix उपयुक्त न हो, तो reverse anchored, reverse suffix, reverse inner optimization आज़माए जाते हैं
- बाकी मामलों में
PikeVM, bounded backtracker, one-pass DFA, lazy DFA, full DFA शामिल करने वाली core strategy पर fallback किया जाता है
- पूरी strategy को दो वाक्यों में समेटा जा सकता है
- जहाँ तक संभव हो literal search करो
- जहाँ तक संभव हो
PikeVMके उपयोग से बचो
- reverse suffix और reverse inner optimization गलत तरीके से इस्तेमाल होने पर haystack के आकार के सापेक्ष worst-case
O(m * n^2)बन सकते हैं- meta engine यह पहचान लेता है कि reverse scan पिछली suffix match के अंत से आगे निकलने वाला है, और समय जटिलता की गारंटी बनाए रखने के लिए core strategy पर fallback कर जाता है
RE2 से अंतर
regexcrate और RE2 में कई समानताएँ हैं- RE2 में भी PikeVM के समकक्ष NFA, bitstate backtracker, one-pass NFA, lazy DFA, और कई इंजनों को मिलाकर चलने वाली संरचना है
- ऊपर के इंजनों में RE2 में जो नहीं है, वह fully compiled DFA है
- मुख्य अंतर इस प्रकार हैं
- RE2 leftmost-first के अलावा POSIX शैली के leftmost-longest semantics को भी option के रूप में support करता है
- RE2 का Unicode support अधिक सीमित है, और
\w,\s,\d,\bको Unicode definitions के अनुसार इस्तेमाल करने का option नहीं है - RE2 में character class set operation में union के अलावा बाकी support सीमित है
- संभव है कि RE2 का PikeVM memory के लिहाज़ से अधिक efficient हो
- RE2 की literal optimization सीमित है, जबकि
regexcrate अधिक literal optimization करता है - RE2 का lazy DFA कई threads के बीच वही transition cache साझा करता है, इसलिए synchronization चाहिए; जबकि
regexcrate thread-वार अलग cache मांगता है, इसलिए अधिक memory इस्तेमाल करता है regexcrateregex-syntaxऔरregex-automataको अलग versioned library के रूप में प्रकाशित करता है, लेकिन RE2 ऐसा support नहीं करताregex-automataसभी इंजनों में first-class multi-pattern regex support देता है, और हर matched pattern के match व capture group offset भी report कर सकता है
test strategy और benchmark
- नई test strategy में अंदरूनी इंजनों को स्वतंत्र first-class API बनाया गया है और हर engine को सीधे test किया जाता है
- सभी regular expression tests TOML files में निर्दिष्ट होते हैं
regex-testcrate TOML tests को पढ़कर structured representation में बदलता है- हर engine configuration के लिए एक Rust unit test रखा जाता है, और उस engine पर लागू होने वाले सभी TOML tests चलाए जाते हैं
- क्योंकि Rust unit test framework extensible नहीं है, इसलिए specific test filtering के लिए अपना environment variable infrastructure जोड़ा गया
- सिर्फ
regex-automataमें ही 450 से अधिक documentation tests हैं regex 1.9की तैयारी के दौरान कई fuzz testing target भी जोड़े गए, और Addison Crump की मदद से कुछ bugs मिले- benchmark
rebarनाम के regex barometer के रूप में प्रकाशित हैंrebarसिर्फregexcrate ही नहीं, कई regular expression engines का benchmark करता है- 242 benchmarks में search time के आधार पर
regex 1.9,regex 1.7.3से औसतन 1.5 गुना तेज़ है - regular expression build करने का समय कुछ हद तक regress हुआ है
1.8एक transition release था जिसमें कुछ migration work शामिल था, इसलिए comparison baseline के रूप में1.7का इस्तेमाल किया गया
लागत और हल्के विकल्प
- इस rewrite में पिछले कुछ वर्षों के दौरान लेखक का अधिकांश free time लगा, और ripgrep जैसे projects कुछ समय तक release नहीं हो पाए
- पुन: उपयोग योग्य public abstraction, internal-only abstraction की तुलना में आमतौर पर अधिक code माँगते हैं
- नतीजतन binary size और compile time बढ़ते हैं
- जब internal engine API को अलग version के रूप में public किया गया, तब API तोड़ने के लिए
regex-automataकी उपयुक्त breaking change release की ज़रूरत पड़ती है - लागत कम करने के लिए दो कदम लागू किए गए
- fully compiled DFA engine को default रूप से disabled रखा गया और opt-in feature बनाया गया
regex-liteको नए crate के रूप में जारी किया गया
regex-liteका लक्ष्यregexcrate का लगभग drop-in replacement बनना है, लेकिन इसका फोकस binary size और compile time optimization पर है- यह Unicode और performance से जुड़ी कुछ सुविधाएँ छोड़ देता है
O(m * n)समय जटिलता की गारंटी बरकरार रखता है- इसकी dependencies 0 हैं, और इसमें अपना regular expression parser शामिल है, इसलिए यह
regexcrate के साथ code share नहीं करता
regex-liteअभी एक experimental mitigation है, लेकिन यह दिखाता है किregexcrate की feature settings में optimization और Unicode सुविधाएँ बंद करने पर भी binary size और compile time के मामले मेंregex-liteके करीब पहुँचना मुश्किल है
1 टिप्पणियां
Hacker News की राय
सिर्फ सरसरी तौर पर देखने पर भी Rust regex crate सच में प्रभावशाली है
BurntSushi ने बहुत-सी शानदार चीजें बनाई हैं, लेकिन Rust का regex crate तो लेजेंडरी है, और Rust ecosystem के लिए यह बड़ा वरदान है कि उसके पास बहुत पहले से ही तेज़ और इस्तेमाल में आसान regular expression library रही है
Russ Cox की regular expression पर लेख-श्रृंखला भी बेहतरीन है; जब regular expressions मुझे theory और practice के एकदम सही संगम जैसे लगने लगे थे, तब एक गर्मी में regular expression engine बनाने के लिए मैंने उनका सहारा लिया था
इस लेख में testing में हुए और गहरे बदलाव भी दिलचस्प हैं, और ecosystem के लिए इतने अहम crate पर ऐसे गहरे विषयों की व्याख्या मिलना सराहनीय है
regular expressions कभी-कभी पढ़ने में मुश्किल होते हैं और email validation जैसी चीजों में उनका दुरुपयोग भी होता है, लेकिन लगभग हर भाषा में वे सबसे घने/कंसाइज़ tools में से एक हैं
व्यावहारिक किताब के तौर पर मैं Jeffrey Friedl की Mastering Regular Expressions को ही अच्छी तरह जानता हूं, और theory वाली तरफ compiler books इसे कवर करती हैं; Dragon Book भी implementation के नजरिए से ठीक थी। regular expressions पर दूसरी किताबों की सिफारिशें जानना चाहूंगा
regular expression के लिए non-deterministic finite automaton बनाते हैं, और फिर उस resulting directed graph में search करके अगर final state से संबंधित vertex तक पहुंचना संभव हो तो उसे match माना जाता है
regular expressions इस्तेमाल करने वाला कोई भी व्यक्ति इस exercise से समझ सकता है कि इसमें अपेक्षा से कम जादू है, और NFA के ऊपर गेंदों को इधर-उधर उछलते सोचें तो production में आखिरकार मिलने वाले catastrophic backtracking bugs का भी भौतिक अर्थ बनने लगता है
मूल लेख के संदर्भ में, https://github.com/rust-lang/regex/issues/822 में BurntSushi की आखिरी टिप्पणी niche API वाले paragraph https://blog.burntsushi.net/regex-internals/#problem-request... में उपयोगी context जोड़ती है। कई regular expressions को text में एक साथ search करना बहुत जटिल है, लेकिन बहुत उपयोगी भी है, इसलिए इस pattern से community क्या बनाएगी, यह देखने की उत्सुकता है
उदाहरण के लिए
header:field1,field2,field3"data"hashजैसे fixed number of fields वाले format, याsuite~split/test1,test2@opt1:opt2^hw1^hw2#flags1#flags2जैसे ऐसे format जिनमें ज्यादातर elements optional हों, उनमेंsplitजैसे basic tools काफी नहीं पड़ते और regular expressions अच्छी तरह fit होते हैंregular expressions जल्दी पढ़ने में मुश्किल क्यों हो जाते हैं, वजह भी यही है। एक ही regular expression में fields के बीच delimiters, हर field की validity, और कौन-सा field optional है—सब घुल-मिल जाता है
ये असल में तीन अलग concerns हैं, लेकिन ज्यादातर regular expression APIs इन्हें step-by-step अलग करने नहीं देतीं और सब कुछ मिलाकर सिर्फ एक string लेती हैं
भाषा की constraints और copyright protection की स्थिति के कारण मुझे RegEx functionality खुद नए सिरे से बनानी पड़ी, और regular expressions सचमुच कभी-कभी voodoo जैसे लगते हैं
मुझे नहीं पता दूसरे engines कितनी बार इस्तेमाल होते हैं, लेकिन अगर बहुत-सी programming languages PikeVM इस्तेमाल करती हैं, तो समझ आता है कि Google ने servers के लिए अपना OS क्यों बनाया और कुछ situations में कुछ clock cycles भी बचाने के लिए तेज़ engine लगाने की कोशिश क्यों की
search string में बस कुछ characters जोड़ने से भी pattern matching काफी धीमी हो जाती है, यह मुझे अच्छी तरह पता है। “छोटे पैसे संभालो, बड़े पैसे अपने-आप संभल जाएंगे” वाली कहावत RegEx और clock cycles पर भी अच्छी बैठती है, और 90s में प्रति सेकंड लाखों records process करने वाली बातचीतों को याद करें तो लगता है इसने कुछ coders को बहुत अमीर बना दिया होगा
खासकर dialect और context के हिसाब से quotes handle करने या expression खत्म करने के तरीके इतने अलग होते हैं कि याद करने की कोशिश छोड़कर जरूरत पड़ने पर हर बार examples search करने लगता हूं
ActiveState में, कॉलेज से अभी-अभी निकले एक colleague के साथ मुझे Komodo editor के लिए regular expression debugger बनाने का काम मिला था
हमने legendary Perl expert Mark Jason Dominus को hire किया ताकि वे Perl regular expression engine में hooks डालें, और उन hooks को UI में expose करके users को regular expression execution step-by-step देखने दें
आजकल web-based tools ज्यादा बेहतर हैं, लेकिन 2001 में Komodo का Rx Debugger cutting-edge था और उस पर काम करना भी काफी मजेदार था
मैं air-gapped network से deal कर रहा था, इसलिए जिन लोगों को tool इस्तेमाल करना था वे online sites access नहीं कर सकते थे, और किसी भी design में work data को online tool में डालने का विचार तक allowed नहीं था
लेकिन ज्यादातर effort online tools पर केंद्रित है, और offline tools https://regex101.com/ जैसी चीजों की तुलना में दुर्लभ और कमजोर हैं
मुझे जिज्ञासा है कि क्या इसे strings के बजाय lists पर भी इस्तेमाल किया जा सकता है
characters की list को खोजने और बदलने के लिए तो शक्तिशाली tools हैं, लेकिन जैसे ही वह numbers या dates की list बन जाती है, सब कुछ गायब हो जाता है—यह हमेशा झुंझलाहट भरा रहा है
उदाहरण के लिए, अगर login attempt dates की list में 5 या उससे ज्यादा failures के बाद success आने वाली सभी sequences ढूंढनी हों, तो regex से यह आसान है, लेकिन असल में आपको खुद loop, flags और temporary lists बनानी पड़ती हैं
list को string में बदलकर process करने और फिर वापस बदलने का रास्ता हो सकता है, लेकिन उसकी कमियां साफ हैं। भले ही वह string-based regex जितना तेज न हो, मुझे समझ नहीं आता कि arbitrary list types के लिए regex क्यों नहीं होना चाहिए
पहले बनाया हुआ Python prototype भी मिला: https://github.com/boppreh/listregex
यह बहुत धीमा है, लेकिन API experiment के तौर पर संतोषजनक है, और pattern inversion, intersection, matching pairs जैसे tools भी देता है जो regex में नहीं होते
ऐसे regex engine में generic alphabet जोड़ना शुरुआत से ही अव्यावहारिक है। खासकर API design और मुख्य use cases की performance को नुकसान न पहुंचाने वाला तरीका व्यावहारिक रूप से बहुत कठिन है
अगर performance की चिंता न हो तो ऐसा regex engine बनाना मुश्किल नहीं है। उदाहरण के लिए, मैंने जो
regex-litecrate प्रकाशित किया है उसे लेकर आप जितना चाहें उतना generic बना सकते हैं, और इस प्रक्रिया में आपको दिलचस्प चुनौतियां मिलेंगीऐसा है कि यह बिल्कुल मौजूद ही नहीं है, ऐसा भी नहीं। लोगों ने बनाने की कोशिश की है[1]। हालांकि वे इसकी सामान्य उपयोगिता को कुछ ज्यादा बढ़ा-चढ़ाकर बताते हैं, इसलिए आम तौर पर इसे बड़ा traction नहीं मिलता लगता :-)
[1]: https://docs.rs/automata/latest/automata/trait.Alphabet.html
std::basic_regexuser-defined character types के लिए template class expose करके ऐसी कोशिश करता है: https://en.cppreference.com/w/cpp/regex/basic_regexआप एक trait class दे सकते हैं जो user-defined “character” के लिए जरूरी behavior define करती है
लेकिन performance काफी गिर जाती है, और इसके arbitrary non-character objects को user-defined
std::basic_stringमें डालने जितना ही अच्छी तरह काम करने की संभावना हैयह असंभव नहीं है, लेकिन ज्यादातर languages के पास इसके लिए अच्छा interface नहीं है
code या text files में कुछ ढूंढते समय मैं Ripgrep रोज इस्तेमाल करता हूं, और Windows, Linux, Mac, VSCode, Vim—जहां भी इस्तेमाल करता हूं, हर बार आभारी होता हूं
यह उन software में से एक है जिसने मेरी जिंदगी और काम करने का तरीका बदल दिया
जब मजबूरी में
grepइस्तेमाल करना पड़ता है, तो ऐसा लगता है जैसे उस दौर में लौट गया हूं जब सब कुछ single-core CPU पर चलता था और data धीमी PATA/IDE spinning hard disk पर होता थाBurntSushi महान programmers के बीच भरपूर सम्मान के हकदार हैं
agथा, उससे पहलेackथा, और इन सभी ने साधारणgrepसे कहीं बेहतर interface देने की कोशिश कीकाम की एक समस्या के लिए बहुत लंबे regex के 1 करोड़ से ज्यादा items से
RegexSetबनाना पड़ा थाकोई भी engine default state में इसे संभाल नहीं पाया, और Rust का
RegexSetभी default values के साथ पर्याप्त नहीं थाफिर भी
regex-automataऔरregex-syntaxका उपयोग करना और code पढ़ना 2018 में भी बहुत उपयोगी learning material थाआखिरकार work project ने Lucene API को model बनाया, लेकिन यह basic foundations regex crates से सीखने के बाद ही संभव हुआ
आगे का काम regex engine को ज्यादा patterns पर बेहतर scale कराने का है। अभी तो यह 1 करोड़ regex से बहुत पहले ही टूट जाएगा, और यह लक्ष्य सच में संभव है या नहीं, इस पर भी भरोसा करना मुश्किल है
फिर भी यह मौजूदा स्थिति से निश्चित रूप से बेहतर हो सकता है
बेशक multi-pattern search में Hyperscan असल में gold standard जैसा है। हालांकि 1 करोड़ patterns को यह कितना अच्छी तरह handle करेगा, पता नहीं
पहले
regex-automatacrate के साथ experiment किया था, और यह एकमात्र library थी जिसे text editor में इस्तेमाल किया जा सकता था क्योंकि internal DFA तक सीधे access मिल जाता थासामान्य regex library APIs input को एक single contiguous string मानती हैं, लेकिन यह तरीका किसी भी text data structure के साथ compatible है
मैं
regex-automatacrate, वह भी शुरुआती0.2.0release इस्तेमाल करने वाला code लिख रहा था, तभी यह लेख आयालगता है अब यह देखने का समय है कि क्या नई internal structure को फिर से खंगालना पड़ेगा
अभी article नहीं पढ़ा है, लेकिन यह बहुत दिलचस्प और timing के लिहाज से बेहद सटीक लगता है
कुछ मिनट बाद देखा तो जवाब “हो सकता है” के करीब था, लेकिन formal release होने की वजह से शायद code को काफी simplify किया जा सकेगा
फिर करीब 10 मिनट बाद यह काफी painless निकला और नया
Builder::patchmethod पूरा upgrade थाP.S. के तौर पर, मैं अभी भी आपके सभी GitHub repositories से blocked हूं, और यह देखते हुए कि कई crates व्यापक रूप से इस्तेमाल होते हैं, मुझे यह थोड़ा unfair लगता है। मूल घटना याद नहीं है। regex crate खुद अब शायद
rust-langorganization के नीचे है, लेकिन अब भी कुछ चीजों के साथ interact नहीं कर सकताregex-automata0.2.0docs में इस बारे में बड़ी warning थी, और0.1इस्तेमाल करने की जोरदार recommendation थी: https://docs.rs/regex-automata/0.2.0/regex_automata/मुझे भी मूल घटना याद नहीं है। कई वजहों से मैं बहुत लोगों को block करता हूं, लेकिन अब block हटा दिया है
BioJulia ने compile time पर arbitrary Julia code डालने में सक्षम pure Julia regex engine Automa.jl जारी किया था
मैं यह कमतर नहीं आंकना चाहता कि Rust का regex, Automa से कहीं ज़्यादा advanced है, लेकिन regex internals को library के रूप में expose करने का यह पहला उदाहरण है—इस बात से सहमत होना मुश्किल है
उदाहरण के लिए, PCRE2 में “callout” support है, जो कही गई बात से मिलता-जुलता लगता है: https://www.pcre.org/current/doc/html/pcre2callout.html
ragelयाre2cजैसी चीज़ें भी इसी तरह का काम करती रही हैंइस ब्लॉग में जिस बात की चर्चा है, वह regex library के अपने internals को निकालकर अलग से versioned library बनाना और दूसरों को उन्हें compose करने देना है
backtracker के लिए यह तरीका आम तौर पर कम स्वाभाविक है, क्योंकि engine में अक्सर सिर्फ एक backtracker होता है, लेकिन automaton-based libraries में कई engines को अलग-अलग तरीकों से combine करना आम बात है
फिर भी backtracker भी असल में ऐसी चीज़ें expose कर सकते हैं जिन्हें वे आम तौर पर expose नहीं करते, जैसे regex parser या AST