# עוגנים והיגדים עוגנים והיגדים מתאימים *מיקומים* ולא תווים. הם בודקים היכן במחרוזת נמצאים, ולא מה נמצא שם. מחלקת תווים פשוטה צורכת תו אחד; עוגן אינו צורך כלום. הם נקראים *היגדים ברוחב אפס* מסיבה זו — רוחבם הוא אפס, אך הם מצהירים שתכונה כלשהי חייבת להתקיים. ## תחילת המחרוזת וסופה `^` מתאים בתחילת המחרוזת. `$` מתאים בסוף, או ממש לפני שורה חדשה סופית. ```perl "housekeeper" =~ /keeper/; # matches — 'keeper' appears somewhere "housekeeper" =~ /^keeper/; # does not match — not at start "housekeeper" =~ /keeper$/; # matches — at end "housekeeper\n" =~ /keeper$/; # matches — before the trailing newline ``` בשימוש משולב, `^…$` כופה על התבנית להסביר את כל המחרוזת: ```perl "bert" =~ /^bert$/; # matches "bertram" =~ /^bert$/; # does not match "dilbert" =~ /^bert$/; # does not match "" =~ /^$/; # matches — empty string ``` עבור מחרוזת מילולית, `$s eq "bert"` מהיר יותר וברור יותר. `^…$` מצדיק את עצמו רק כשהאמצע משתמש בתכונות regex אמיתיות. ## עוגנים מוחלטים לעומת עוגנים יחסיים לשורה תחת המתאם `/m`, `^` ו־`$` מתעגנים בכל גבול שורה בתוך המחרוזת, ולא רק בקצוות החיצוניים: ```perl my $x = "There once was a girl\nWho programmed in Perl\n"; $x =~ /^Who/; # does not match — 'Who' is not at string start $x =~ /^Who/m; # matches — 'Who' is at start of second line ``` עבור הקצוות המוחלטים *ללא תלות ב־`/m`*, יש להשתמש בעוגנים הייעודיים: - `\A` — תחילת מחרוזת מוחלטת. - `\z` — סוף מחרוזת מוחלט. - `\Z` — סוף המחרוזת, או ממש לפני שורה חדשה סופית. דומה ל־`$` אך אינו מושפע מ־`/m`. ```perl $x =~ /^Who/m; # matches, as above $x =~ /\AWho/m; # does not match — \A is string start, always $x =~ /girl$/m; # matches, end of first line $x =~ /girl\Z/m; # does not match — 'girl' is not at string end $x =~ /Perl\Z/m; # matches — end or before final newline $x =~ /Perl\z/m; # does not match — \z is strict end ``` כלל אצבע: `\A` ו־`\z` כשמתכוונים לכל המחרוזת; `^` ו־`$` כשמתכוונים לשורות (בדרך כלל עם `/m`). `\A` ו־`\z` גם מהירים יותר: המנוע יודע שהם מגבילים מיקום, ומדלג על ניסיונות התקדמות צעדית ש־`^` מתיר. ארבעת מצבי השורה החדשה ניתנים לסיכום בטבלה. יש לבחור את זה התואם למה ש־`^`, `$` ו־`.` אמורים להיות עבור הקלט: | מצב | `^` מתאים ב־ | `$` מתאים ב־ | `.` מתאים ל־`\n`? | |------------|-------------------|------------------------------|---------------------| | ברירת מחדל | תחילת מחרוזת בלבד | סוף מחרוזת (ולפני `\n` סופי) | לא | | `/m` | תחילת כל שורה | סוף כל שורה | לא | | `/s` | תחילת מחרוזת בלבד | סוף מחרוזת (ולפני `\n` סופי) | כן | | `/sm` | תחילת כל שורה | סוף כל שורה | כן | `\A`, `\Z`, `\z` אינם מושפעים מאף אחד מהמתאמים. ## גבולות מילה `\b` מתאים למיקום בין תו מילה (`\w`) לתו שאינו תו מילה (`\W`), או בין אחד מהם לבין קצה המחרוזת. הוא אינו צורך אף תו. ```perl my $x = "Housecat catenates house and cat"; $x =~ /cat/; # matches 'cat' inside 'Housecat' $x =~ /\bcat/; # matches 'cat' inside 'catenates' $x =~ /cat\b/; # matches 'cat' inside 'Housecat' $x =~ /\bcat\b/; # matches the standalone 'cat' at end ``` `\B` הוא השלילה — מיקום שאינו גבול מילה. `\b` מחוץ ל־`[…]` הוא גבול מילה. `\b` *בתוך* `[…]` הוא תו ה־*backspace*, `\x08`. המשמעות הכפולה היא המלכודת הנפוצה ביותר בתחביר ה־regex. יש לקרוא תמיד את ההקשר של `\b` לפני קריאת מה שהוא עושה. ### `\b` ופריטים שמתחילים ב־`\W` — מלכודת ה־`$3.75` `\b` דורש מעבר בין תווי מילה לתווים שאינם תווי מילה. אם שני צדי המיקום הם תווים שאינם תווי מילה, אין גבול. זה תופס אנשים הבונים תבניות באמצעות שיבוץ: ```perl my $item = '$3.75'; my $regex = qr/\b\Q$item\E\b/; "is $3.75 plus tax" =~ /$regex/; # does NOT match ``` מדוע? לאחר ש־`\Q$item\E` מתבשל, התבנית היא `\b\$3\.75\b`. המנוע מחפש מיקום מיד *לפני* `$` שבו צד אחד של המיקום הוא תו מילה. התו שלפני `$` בקלט הוא רווח (לא־תו־מילה), ו־`$` עצמו אינו תו מילה. אין מעבר מילה/לא־מילה; `\b` נכשל. כדי לעגן את תחילתו של `$item` ללא תלות בתו הראשון שלו: ```perl my $regex = qr/(?:^|\s)\Q$item\E(?:\s|$)/; ``` יש להשתמש בעוגני קצה־מחרוזת או רווח לבן כאשר התוכן המשובץ עשוי להתחיל בתו שאינו תו מילה. `\b` מיועד להקשרים שבהם *ידוע* שהטקסט המוקף הוא דמוי־מילה. ### וריאנטים של גבול מילה ב־Unicode `\b` רגיל פועל לפי ההגדרה של `\w`/`\W`, אשר תחת Unicode מכסה אותיות, ספרות, סימני ניקוד וסימני פיסוק מחברים. טקסט אמיתי — שפה טבעית עם אפוסטרופים, מקפים, מספרים עם פסיקים — דורש ניואנסים נוספים. Perl מספקת ארבעה וריאנטים סמנטיים של גבול: | גבול | משמעות | |-----------|----------------------------------------------------------| | `\b{wb}` | גבול מילה ב־Unicode — מטפל ב־`don't`, `state-of-the-art` | | `\b{sb}` | גבול משפט | | `\b{lb}` | שבירת שורה (מתאים לעטיפת שורות) | | `\b{gcb}` | גבול אשכול גרפמות (אותה השפעה כמו `\X`) | ```perl "don't" =~ /.+?\b{wb}/x; # matches whole word — apostrophe is inside "don't" =~ /.+?\b/x; # stops at the apostrophe — plain \b splits ``` עבור עיבוד שפה טבעית, `\b{wb}` ו־`\b{sb}` הם כמעט תמיד הרצויים. `\b` רגיל מיועד להקשרי מזהי ASCII. ## `\G` — מיקום ההתאמה האחרון `\G` מתעגן במיקום שבו הסתיימה ההתאמה המוצלחת הקודמת של `/g` באותה מחרוזת. הוא מה שהופך טוקניזציה עם `/g` בלולאות `while` ליציבה. ```perl my $s = "12abc34"; while ($s =~ /\G(\d+|[a-z]+)/gc) { print "got '$1'\n"; } # prints: '12', 'abc', '34' ``` ללא `\G`, אותה תבנית הייתה סורקת מחדש ממקום שבו היא התאימה לראשונה, מדלגת על תווים שלא התאימו — ומאבדת נתונים בשקט. המתאם `/gc` משמר את המיקום בכישלון; מכוסה בפרק [modifiers](modifiers.md). ### מדוע `\G` חשוב: הבחירה בין סנכרון לדילוג דוגמה מעובדת של פרידל. מציאת כל מיקוד בן חמש ספרות שמתחיל ב־`44` במחרוזת רצה: ```perl my $s = "06192054410-44272-13901-44106-22134"; while ($s =~ /44(\d{3})/g) { print "got 44$1\n"; } ``` ללא `\G`, ההתקדמות הצעדית של המנוע שמחה למצוא את `44272` ו־`44106` נכונות *וגם* למצוא את `44272` ממיקום התחלה אחר בכל פעם שהתאמה קודמת נדחתה. הפלט הוא ״התשובה הנכונה בתוספת נכונות־לא נוספות״. עם `\G` בכל איטרציה, המנוע אינו יכול להתקדם צעדית בכישלון — עליו להמשיך *בדיוק* היכן שהסתיימה ההתאמה האחרונה. כישלון מסיים את הלולאה: ```perl while ($s =~ /\G\D*(44\d{3})/gc) { print "got $1\n"; } ``` `\G` מבטל למעשה את ההתקדמות הצעדית, וזה מה שטוקניזציה מסונכרנת דורשת. הכלל הכללי: **אם נכונות הלולאה תלויה בכך שכל התאמה צמודה לקודמתה, נדרש `\G`**. `\G` קורא את הערך ש־`pos($string)` מחזיר. הצבת `pos` במפורש מאפסת את העוגן. באיטרציה הראשונה של לולאת `/g` (או כל תבנית שטרם הותאמה למחרוזת זו), `\G` שקול ל־`\A`. ## Lookahead Lookahead בודק מה בא אחר כך מבלי לצרוך אותו. lookahead חיובי הוא `(?=…)`: ```perl my $x = "I catch the housecat 'Tom-cat' with catnip"; $x =~ /cat(?=\s)/; # matches 'cat' in 'housecat' — space follows $x =~ /cat(?!\s)/; # matches 'cat' in 'catch' — no space follows ``` `(?!…)` הוא lookahead שלילי: התאמה רק אם התבנית הפנימית *אינה* חלה. רוחב אפס, בדיוק כמו `^` ו־`$`. ```perl "foobar" =~ /foo(?!bar)/; # does not match "foobaz" =~ /foo(?!bar)/; # matches ``` ### דוגמה מעובדת של lookahead: ספרות שאינן מלוות בנקודה שאלה שנשמעת טבעית אך קשה ממה שנראה: ״להתאים רצפי ספרות שאינם מלווים בנקודה.״ הניסיון הראשון: `\d+(?!\.)`. בהחלתו על `OH 44272`: ```perl "OH 44272" =~ /\d+(?!\.)/; # MATCHES, matches '44272' ``` זה נראה נכון, אך מהסיבה הלא־נכונה. ה־`\d+` החמדן תפס תחילה את כל רצף הספרות `44272`; התו הבא הוא סוף־מחרוזת (לא `.`), ולכן `(?!\.)` הצליח מיד — ה־lookahead במקרה התקיים מבלי שהמנוע נדרש לסגת. הקריאה ה *אינטואיטיבית* ״רצף הספרות מסתיים במשהו שאינו נקודה״ מתקיימת במקרה של ספרות בסוף, אך הדוגמה הבאה מראה מה קורה כשהיא לא מתקיימת. כעת בהחלתו על `4423.45`: ```perl "4423.45" =~ /\d+(?!\.)/; # matches '442', not '4423' ``` הניסיון הראשון של המנוע: `\d+` מתאים ל־`4423`. `(?!\.)` בודק את התו הבא: הוא `.`, ה־lookahead השלילי נכשל. המנוע חוזר לאחור: `\d+` מתאים ל־`442`, התו הבא הוא `3`, לא `.`, הצלחה. התבנית מתאימה ל־`442` — *לא* רצף הספרות המלא שלפני הנקודה. המכמת החמדן היה מוכן לסגת כדי לאפשר ל־lookahead להצליח. התיקון: לדרוש שה־lookahead ידחה גם ספרות אחרות, כך שהרצף לא יוכל לסגת ל״ספרה ואחריה ספרה״. ```perl "4423.45" =~ /\d+(?![\d.])/; # matches '45' — correct ``` `\d+(?![\d.])` נקרא ״התאם רצף ספרות שאינו מלווה בספרה אחרת או בנקודה״. המכמת החמדן עדיין מתפשט מקסימלית, אך ה־lookahead כעת מסרב להצליח באמצע רצף ספרות, ולכן המיקומים המתאימים היחידים הם קצוות רצף הספרות בפועל. הלקח מתכלל: **lookahead שמוציא רק תו אחד יכול להיות מובס על־ידי מכמת חמדן הנסוג**. יש לכלול תמיד את תווי ה *אטום* שהמכמת מתאים בשלילת ה־lookahead. מקרה קשור: lookahead שלילי מוביל וההתקדמות הצעדית. ```perl "cattle" =~ /(?!cat)\w+/; # MATCHES, captures 'cattle' ``` ה־lookahead `(?!cat)` דוחה את מיקום 0 (שבו `cat` היה מתאים). המנוע מתקדם צעדית: מיקום 1 מתחיל ב־`attle`, שבו `(?!cat)` מצליח, ו־`\w+` מתאים ל־`attle`. כדי לכפות ״המילה הראשונה שאינה מתחילה ב־`cat`״, יש לעגן את ה־lookahead בגבול מילה: ```perl "cattle" =~ /\b(?!cat)\w+/; # does not match ``` כעת ה־lookahead חל בכל גבול מילה, ובמילה `cattle` זהו רק מיקום 0. ## Lookbehind Lookbehind בודק מה היה קודם. lookbehind חיובי הוא `(?<=…)`: ```perl "cats and dogs" =~ /(?<=and )dogs/; # matches 'dogs' after 'and ' ``` lookbehind שלילי הוא `(?…)` | כן | כן | לא | לא | לא | למעשה, רק PCRE2 (ומנועים אחרים שמקורם ב־Perl מחוץ לטבלה — Java, Python, ‎.NET) תומך ב־lookaround. כלי POSIX ו־RE2 / Go פשוט אינם תומכים בו. תבניות הסומכות על lookaround אינן ניידות מעבר לפער הזה. ## סיכום | עוגן | מתאים | |-----------|-----------------------------------------------| | `^` | תחילת מחרוזת, או תחילת שורה תחת `/m` | | `$` | סוף מחרוזת (לפני `\n` סופי), או שורה תחת `/m` | | `\A` | תחילת מחרוזת מוחלטת | | `\z` | סוף מחרוזת מוחלט | | `\Z` | סוף מחרוזת או לפני `\n` סופי, מתעלם מ־`/m` | | `\b` | גבול מילה | | `\B` | שאינו גבול מילה | | `\b{wb}` | גבול מילה ב־Unicode | | `\b{sb}` | גבול משפט | | `\b{lb}` | שבירת שורה | | `\b{gcb}` | גבול אשכול גרפמות | | `\G` | סוף ההתאמה הקודמת של `/g` (או `\A` אם אין) | | `(?=…)` | lookahead חיובי | | `(?!…)` | lookahead שלילי | | `(?<=…)` | lookbehind חיובי | | `(?…)`, פעלי בקרת חזרה לאחור מיוחדים. - פרק [cross-engine](cross-engine.md) — טבלת תאימות מלאה של עוגנים ו־lookaround. - [`split`](../../p5/core/perlfunc/split.md) — פיצול במיקומים ברוחב אפס. - [`pos`](../../p5/core/perlfunc/pos.md) — קריאה או קביעה של המיקום שבו `\G` מתעגן.