Phasers: BEGIN, UNITCHECK, CHECK, INIT, END#

חמישה בלוקים בעלי שם מיוחד מאפשרים לקוד להתחבר למעברים שתוכנית עושה בין היותה מהודרת לבין היותה מורצת. הם נראים כמו שגרות אך אינם כאלה: ניתן לכתוב כמה מכל סוג, לאף אחד אין שם הניתן לקריאה, וכל אחד מהם נורה אוטומטית ברגע שלו.

BEGIN     { ... }   # during compilation, as soon as defined
UNITCHECK { ... }   # right after this compilation unit is compiled
CHECK     { ... }   # after the whole program is compiled
INIT      { ... }   # just before the runtime begins
END       { ... }   # just before the interpreter exits

התחילית sub (sub BEGIN { ... }) מתקבלת אך אינה מוסיפה דבר ואין לעודד אותה. אלה בלוקים, לא שגרות בעלות שם; לא ניתן לקרוא להם, אלא רק לתת להם להירות.

מתי כל אחד רץ#

תוכנית פרל חיה בשני שלבים: שלב הידור שמפרש את המקור לצורה פנימית, ושלב ריצה שמבצע אותו. ה-phasers מסמנים את הגבול.

  • BEGIN רץ ברגע שסוגר הסוגר שלו מפורש, עוד לפני ששאר הקובץ בכלל נקרא. כך פועל use: use הוא require עטוף ב-BEGIN בתוספת import, כך שהשמות המיובאים קיימים עבור הקוד שלאחר מכן. לאחר ש-BEGIN רץ, הוא אינו מוגדר וזיכרונו משוחזר.

  • UNITCHECK רץ מיד כשיחידת ההידור שמכילה אותו סיימה להתהדר. יחידה היא התוכנית הראשית, מודול שנטען על ידי require, eval של מחרוזת, do FILE, או קוד שהודר על ידי מבנה ה-regex (?{ }). משום שיחידות מסיימות להתהדר בזמנים שונים, UNITCHECK הוא ה-phaser היחיד שתזמונו עוקב אחר הקובץ הבודד ולא אחר התוכנית בכללותה.

  • CHECK רץ פעם אחת, אחרי ששלב ההידור הראשוני של כל התוכנית מסתיים ולפני ששלב הריצה מתחיל.

  • INIT רץ פעם אחת, רגע לפני ששלב הריצה מתחיל, אחרי כל בלוקי ה-CHECK.

  • END רץ פעם אחת, מאוחר ככל האפשר: אחרי שהתוכנית סיימה (כולל אחרי die), רגע לפני שהמפרש יוצא.

BEGIN ו-UNITCHECK קשורים לפירוש, לא לשלב המפרש, ולכן הם יכולים להירות במהלך כל שלב: eval של מחרוזת בזמן ריצה עדיין מהדר את גופו, וכל BEGIN או UNITCHECK שבתוכו רץ אז ושם. CHECK ו-INIT, לעומת זאת, מסמנים את גבול ההידור/הריצה החד-פעמי של התוכנית הראשית.

ריבוי וסדר ביצוע#

ניתן לכתוב כל מספר של כל בלוק. כולם רצים, בסדר מוגדר:

  • BEGIN: ראשון נכנס, ראשון יוצא (FIFO) כפי שנתקלים בו במהלך ההידור. ה-BEGIN הראשון בסדר המקור רץ ראשון.

  • UNITCHECK: אחרון נכנס, ראשון יוצא (LIFO) בתוך כל יחידה, אחרי שאותה יחידה מתהדרת.

  • CHECK: LIFO, אחרי כל ההידור.

  • INIT: FIFO, רגע לפני זמן הריצה.

  • END: LIFO ביציאה. ה-END האחרון שהוגדר הוא הראשון לרוץ.

המקרה המאלף הוא קובץ שמערבב משפטים רגילים עם כל חמשת סוגי הבלוקים. הבלוקים אינם רצים במקום שבו הם נכתבים; הם רצים בשלב שלהם, בסדר שלהם בכל שלב, בעוד המשפטים הרגילים רצים בין לבין בזמן ריצה. מבחינה רעיונית, עבור קובץ בודד:

1. BEGIN blocks      -- FIFO, during compilation
2. UNITCHECK blocks  -- LIFO, after this unit compiles
3. CHECK blocks      -- LIFO, after all compilation
4. INIT blocks       -- FIFO, just before runtime
5. ordinary code     -- run phase, in source order
6. END blocks        -- LIFO, at exit

כך ש-BEGIN ו-UNITCHECK רצים לפני כל CHECK; CHECK רץ לפני INIT; INIT רץ לפני השורה הראשונה של קוד רגיל; וכל END רץ אחרי האחרונה. בתוך BEGIN ו-INIT, בלוקים שנכתבו מוקדם יותר רצים מוקדם יותר; בתוך UNITCHECK, CHECK ו-END, בלוקים שנכתבו מאוחר יותר רצים מוקדם יותר.

בלוקי END בפירוט#

END הוא ה-phaser של הניקוי. הוא רץ LIFO כך שהפירוק מתפרק בסדר ההפוך לזה של ההתקנה, באופן שמשאבים מקוננים מצפים לו.

בתוך בלוק END, $? מחזיק את הערך שהתוכנית עומדת להעביר ל-exit. תוכל לקרוא אותו כדי ללמוד כיצד התוכנית מסתיימת, ותוכל להשים לתוכו כדי לשנות את קוד היציאה:

END {
    $? = 0 if $? == 42;     # rewrite one exit code on the way out
}

היזהר מדריסה של $? בטעות. הרצת כל דבר באמצעות system בתוך בלוק END דורסת את $? עם סטטוס תהליך-הבן, שאז הופך לקוד היציאה של התוכנית אלא אם תשמור ותשחזר אותו.

בלוקי END אינם רצים בכמה מקרים:

  • תחת המתג -c (בדיקת תחביר בהידור-בלבד). הקוד הראשי אינו רץ, וגם בלוקי END אינם רצים.

  • כאשר ההידור נכשל. תוכנית שאינה מתהדרת לעולם אינה מגיעה לנקודה שבה בלוקי END היו נורים.

  • כאשר התהליך מחליף את עצמו באמצעות exec. התמונה איננה; לא נותר דבר שירוץ את הבלוק.

  • כאשר התהליך נהרג על ידי אות קטלני שאינך תופס. תפוס את האות בעצמך (באמצעות %SIG) אם דרוש לך שהניקוי ירוץ.

בלוק END שנוצר בתוך eval של מחרוזת אינו רץ כש-eval זה מסתיים. הוא נרשם כמו כל בלוק END אחר של החבילה שלו ורץ בסדר LIFO רגע לפני שהמפרש יוצא.

${^GLOBAL_PHASE} והשלבים#

המשתנה לקריאה בלבד ${^GLOBAL_PHASE} נוקב בשם השלב הנוכחי של המפרש. ערכיו מאפשרים לקוד שרץ ביותר משלב אחד לגלות היכן הוא נמצא. במהלך בלוק CHECK הוא קורא CHECK; במהלך INIT, INIT; במהלך קוד רגיל בזמן ריצה, RUN; במהלך בלוק END, END. ראה ${^GLOBAL_PHASE} לרשימה המלאה של הערכים ולציר הזמן של השלבים.

בלוקי defer#

בלוק defer הוא בן-הדוד בקנה-מידה קטן של END. הוא רץ כשתחום הבלוק העוטף יוצא, מכל סיבה שהיא, ולא כשהתוכנית כולה יוצאת. פנה אל defer כדי לזווג רכישה עם שחרור בתוך שגרה או לולאה; פנה אל END עבור פירוק כלל-תהליכי שחייב לקרות פעם אחת, ממש בסוף.

ראו גם#

  • BEGIN and use - use הוא טעינה-וייבוא עטופים ב-BEGIN; הסיבה הנפוצה ביותר להתעניין בתזמון שלב ההידור.

  • ${^GLOBAL_PHASE} - קרא את השלב הנוכחי מתוך כל בלוק כדי ללמוד היכן אתה נמצא.

  • defer - ניקוי בתחום הבלוק, הכלי הנכון כשהטווח הכלל-תוכניתי של END רחב מדי.

  • $? - סטטוס היציאה שבלוק END יכול לקרוא ולכתוב מחדש.

  • %SIG - תפוס את האותות שאחרת היו עוקפים את בלוקי ה-END כליל.

  • exit - מה שבלוקי END רצים רגע לפניו; הערך שהוא נושא הוא ה-$? שבלוק END רואה.