1 / 63

תורת הקומפילציה 236360 הרצאה 2 ניתוח תחבירי ( Parsing )

תורת הקומפילציה 236360 הרצאה 2 ניתוח תחבירי ( Parsing ). Wilhelm, and Maurer – Chapter 8 Aho, Sethi, and Ullman – Chapter 4 Cooper and Torczon – Chapter 3. בשבוע הבא לא תתקיים הרצאה. במילים אחרות, אין צורך להגיע לאודיטוריום 2 בבוקר יום חמישי הבא. front-end שלב הניתוח. תוכנית מקור

mare
Download Presentation

תורת הקומפילציה 236360 הרצאה 2 ניתוח תחבירי ( Parsing )

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. תורת הקומפילציה 236360הרצאה 2 ניתוח תחבירי (Parsing) Wilhelm, and Maurer – Chapter 8 Aho, Sethi, and Ullman – Chapter 4 Cooper and Torczon – Chapter 3

  2. בשבוע הבא לא תתקיים הרצאה במילים אחרות, אין צורך להגיע לאודיטוריום 2 בבוקר יום חמישי הבא.

  3. front-end שלב הניתוח תוכנית מקור Back end Lexical Analysis token string syntax analysis Parsing symbol table error messages parse tree semantic analysis decorated syntax tree

  4. האינטרקציה בין המנתח לקסיקלי וה-parser תוכנית מקור error message manager Lexical analysis get next token token parser

  5. ניתוח סינטקטי • המטרה:להבין את המבנה של התוכנית. • למשל:תוכנית C בנויה מפונקציות, כל פונקציה בנויה מהצהרות ופקודות, כל פקודה בנויה מביטויים וכו'. • צורה פשוטה אך מדויקת לניסוח מבנה של תוכנית (בשפת תוכנית ספציפית)היא ע"י דקדוק חסר הקשר. • אנו נתעניין במחלקות של דקדוקים ח"ה שניתן לנתח ביעילות. • פרטים בהמשך... • ה-parser יקרא את רצף ה-tokens, יוודא שהם מקיימים את הדקדוק (או יתריע על שגיאות), ויבנה את עץ הגזירה של התוכנית.

  6. דקדוק חסר הקשר G=(V, T, P, S) • V– nonterminals, משתנים • T– terminals, טרמינלים, tokens • P – חוקי גזירה P = V (VU T)* • S – משתנה תחילי S V

  7. דוגמא:דקדוק ח"ה עבור ביטוי אריתמטי • סִימוּן מָלֵא: • סימון מקוצר:

  8. שפה חסרת הקשר • גזירה – סדרה של החלפות של אותיות לא טרמינליות תוך שימוש בחוקי הגזירה • שפה – אוסף ביטויים הנגזרים מהמצב התחילי והמכילים טרמינלים בלבד סימון הגזירה

  9.    E E E E E A E E A E E A E E A E id id + id + id שפה חסרת הקשר • גזירה – סדרה של החלפות של אותיות לא טרמינליות תוך שימוש בחוקי הגזירה • שפה – אוסף ביטויים הנגזרים מהמצב התחילי והמכילים טרמינלים בלבד • תבנית פסוקית (sentential form) – תוצאת סדרת גזירות בה נותרו (אולי) לא-טרמינלים E *id + E • גזירה שמאלית – גזירה בה מוחלף בכל שלב הסימן השמאלי ביותר (באופן דומה – גזירה ימנית) ניתן לגזירה (רב שלבית) E

  10. דוגמא לגזירה bottom-up • נתון דקדוק S → a A c B e A → A b | b B → d • קלט – a b b c d e a b b c d e • נבחר בשמאלית a A b c d e • נבחר בשמאלית a A c d e a A c B e  A A 3 אלטרנטיבות B  A A 3 אלטרנטיבות B B  S

  11. דוגמא לגזירה bottom-up • נתון דקדוק S → a A c B e A → A b | b B → d • קלט – a b b c d e a b b c d e • נבחר בשמאלית a A b c d e • נבחר בשמאלית a A c d e a A c B e  A A 3 אלטרנטיבות B  A A 3 אלטרנטיבות B B  S

  12. דוגמא לגזירה bottom-up • נתון דקדוק S → a A c B e A → A b | b B → d • קלט – a b b c d e a b b c d e • נבחר בשמאלית a A b c d e • נבחר בשמאלית a A c d e a A c B e  A A 3 אלטרנטיבות B  A A 3 אלטרנטיבות B B  S

  13. דוגמא לגזירה bottom-up • נתון דקדוק S → a A c B e A → A b | b B → d • קלט – a b b c d e a b b c d e • נבחר בשמאלית a A b c d e • נבחר בשמאלית a A c d e a A c B e  A A 3 אלטרנטיבות B  A A 3 אלטרנטיבות B B  S

  14. דוגמא לגזירה bottom-up • נתון דקדוק S → a A c B e A → A b | b B → d • קלט – a b b c d e a b b c d e • נבחר בשמאלית a A b c d e • נבחר בשמאלית a A c d e a A c B e A A 3 אלטרנטיבות B A A 3 אלטרנטיבות B B S האם קיבלנו גזירה ימנית או שמאלית?

  15. קיבלנו גזירה ימנית • S  a A c B e  a A c d e  a A b c d e  a b b c d e • מלמטה תמיד בחרנו בכלל השמאלי ביותר, ולכן הגזירה המתקבלת (מלמעלה)בוחרת קודם בכלל הימני ביותר, כלומר ימנית. • נראה גם בעתיד ניתוחי תחביריים שהולכים על הקלט משמאל לימין ויוצרים גזירה ימנית. ניתוחים אלה מסומנים כ-LR והם עובדים בשיטת ה-bottom-up. • ניתוחים שהולכים על הקלט משמאל לימין ומייצרים גזירה שמאלית מסומנים ב-LL והם עובדים לפי top-down.

  16. ביטוי רגולרי מול דקדוק חסר הקשר • שפות חסרות הקשר יותר חזקות משפות רגולריות. בפרט, כל מבנה שניתן לבטא ע"י ביטוי רגולרי יכול להיות מתואר ע"י דקדוק. • ההיפך לא נכון (למשל?) • מדוע לא לעשות הכל בדקדוק? • כרגיל:הפרדה, מודולריות, פישוט. • אין טעם להפעיל כלים חזקים (ופחות יעילים) על ביטויים רגולריים שקל לנתח. • ביטוים רגולרים שימושיים עבור תאור מבנים לקסיקלים כ- identifiers, קבועים, מילות מפתח וכו' • דקדוקים שימושיים עבור מבנים מקוננים כסוגרים מאוזנים, התאמת begin-end, if-then-else, וכו'

  17. סוגי הניתוח התחבירי • כל דקדוק חסר-הקשר שקול לאוטומט מחסנית (אי-דטרמיניסטי). • אבל – לא שמיש כי לא שקול לאוטומט מחסנית דטרמיניסטי. • באופן כללי: האלגוריתם Cocke-Younger-Kasami מתאים לגזירת כל דקדוק אבל בסיבוכיותO(n3). אנו נעבוד עם אלגוריתמים לינאריים. • חישבו על תוכנית של מאות אלפי שורות קוד... • המטרה • פענוח ובנית עץ הגזירה תוך מעבר בודד על הקלט, משמאל לימין • בכל שלב, הקלט w מתחלק ל x y החלק שטרם נקרא חלק הקלט שקראנו

  18. סוגי הניתוח התחבירי (המשך) • top-down – מהשורש לעלים (נקרא גם – "ניתוח תחזית" – predictive) • bottom-up – מהעלים לשורש – מעבירים למחסנית, או מחליפים צד ימין בסימן מהצד השמאלי של חוק הדקדוק (נקרא גם shift reduce). s  x y s x y

  19. נתחיל בניתוח top-down • נתונים לנו דקדוק ומילה. • רוצים להחליט ע"י מעבר על המילה, כיצד המשתנה התחילי S נגזר, ואח"כ תוך-כדי סריקת המילה, כיצד ממשיכים לגזור כל משתנה עד שמגיעים לטרמינלים. • בדקדוק כללי תיתכן יותר מאפשרות אחת לגזור את המשתנה השמאלי ביותר לפי האות הבאה בקלט. • אנו נשתדל לעבוד עם דקדוקים שבהם תהיה רק אפשרות אחת, אולי נצטרך lookahead קטן כדי להחליט מהו כלל הגזירה המתאים הבא.

  20. דקדוק שקל (מאד)לנתח • התבוננו בדקדוק: E → LIT | ( E OP E ) | not E LIT → true | false OP → and | or | xor בכל שלב של הגזירה, אם מסתכלים על משתנה שצריך לגזור והאות הבאה בקלט, ברור מה כלל הגזירה שצריך להפעיל ! למשל איך נגזרת המילה not ( not true or false ) ? E E not E => not E => not ( E OP E ) => not ( not E OP E ) => not ( not LIT OP E ) => not ( not true OP E ) => not ( not true or E ) => not ( not true or LIT ) => not ( not true or false ) E E ( ) OP not LIT LIT or false true

  21. ניתוח top-down תוך כדי הפעלת פונקציות:Recursive Descent • אלגוריתם Recursive Descent מתרגם דקדוק באופן הבא: • מטרה:להתחיל במשתנה התחילי ולמצוא גזירה. • עבור כל משתנה בדקדוק (nonterminal) מגדירים פונקציה. • המנתח מתחיל לפעול מהפונקציה המתאימה ל-nonterminal התחילי. • כל פונקציה מחקה את החוק שעבורו הוגדרה, באופן הבא: • terminal מתורגם לקריאת האסימון המתאים מהקלט ובדיקת התאמה. • nonterminal מתורגם להפעלת הפונקציה המתאימה לו. • אם ישנם כמה חוקי גזירה עבור אותו nonterminal, בוחרים ביניהם בעזרת lookahead.

  22. פונקציית עזר: match void match(token t) { if (current == t ) current = next_token(); else error; } • פונקציה זו משמשת כדי לקרוא טרמינלים ולבדוק התאמה. • המשתנה current מחזיק את התו (האסימון) שעליו מסתכלים כרגע בקלט.

  23. כתיבת פונקציות בהתאם לדקדוק • למשל, עבור הדקדוק: E → LIT | ( E OP E ) | not E LIT → true | false OP → and | or | xor • נגדיר שלוש פונקציות: E, LIT ו-OP

  24. כתיבת פונקציות בהתאם לדקדוק void E() { if (current  {TRUE, FALSE}) // E → LIT LIT(); else if (current = LPAREN) // E → ( E OP E ) match(LPARENT); E(); OP(); E(); match(RPAREN); else if (current = NOT) // E → not E match(NOT); E(); else error; } }

  25. כתיבת פונקציות בהתאם לדקדוק void LIT() { if (current = TRUE) match(TRUE); else if (current = FALSE) match(FALSE); else error; }

  26. כתיבת פונקציות בהתאם לדקדוק void OP() { if (current = AND) match(AND); else if (current = OR) match(OR); else if (current = XOR) match(XOR); else error; }

  27. סך-הכל: כתיבת פונקציות בהתאם לדקדוק void E() { if (current  {TRUE, FALSE}) LIT(); else if (current = LPAREN) match(LPARENT); E(); OP(); E(); match(RPAREN); else if (current = NOT) match(NOT); E(); else error; } E → LIT | ( E OP E ) | not E LIT → true | false OP → and | or | xor void LIT() { if (current = TRUE) match(TRUE); else if (current = FALSE) match(FALSE); else error; } void OP() { if (current = AND) match(AND); else if (current = OR) match(OR); else if (current = XOR) match(XOR); else error; }

  28. סך-הכל: כתיבת פונקציות בהתאם לדקדוק void E() { if (current  {TRUE, FALSE}) LIT(); else if (current = LPAREN) match(LPARENT); E(); OP(); E(); match(RPAREN); else if (current = NOT) match(NOT); E(); else error; } E → LIT | ( E OP E ) | not E LIT → true | false OP → and | or | xor void LIT() { if (current = TRUE) match(TRUE); else if (current = FALSE) match(FALSE); else error; } הפונקציות האלו נראות כאילו הן לא עושות כלום (חוץ מלזהות שגיאות), אבל ניתן כמובן להוסיף לכל אחת קוד נוסף, למשל שבונה את העץ כך שיוחזר העץ המלא של הגזירה ביציאה מן הרקורסיה. void OP() { if (current = AND) match(AND); else if (current = OR) match(OR); else if (current = XOR) match(XOR); else error; }

  29. הוספת פעולות במהלך הגזירה • בכל פעם שנקראת אחת הפונקציות (למשל, E(), LIT() ו-OP() בדוגמא שלנו), פירוש הדבר ש"איתרנו" צעד בגזירה. • בכל צעד כזה ניתן לבצע פעולות שונות. • הפעולות האלו נקראות "פעולות סמנטיות"ונרחיב עליהן בשיעור נפרד. • בפרט, ניתן לבנות את עץ הגזירה: • כל פונקציה תחזיר רשומה מסוג Node (צומת בעץ). • כל רשומה כזו מכילה רשימה של בנים. • בכל קריאה לפונקציה אחרת (או ל-match), מוסיפים את תוצאת הקריאה ל-Node שנבנה כעת.

  30. הוספת פעולות במהלך הגזירה Node E() { result = new Node(); result.name = “E”; if (current  {TRUE, FALSE}) // E → LIT result.addChild(LIT()); else if (current = LPAREN) // E → ( E OP E ) result.addChild(match(LPARENT)); result.addChild(E()); result.addChild(OP()); result.addChild(E()); result.addChild(match(RPAREN)); else if (current = NOT) // E → not E result.addChild(match(NOT)); result.addChild(E()); else error; return result; }

  31. ואם נחזור לדוגמא • אז, למשל: input = “(not true and false)”; Node treeRoot = E(); E ( E OP E ) LIT not LIT and true false

  32. Recursive Descent גנרי • נחזור לרגע לצורה הבסיסית. לכל משתנה דקדוק A יש פרוצדורה הנראית כך: Void A() { Choose an A-production, A -> X1X2…Xk; for (i=1; i≤ k; i++) { if (Xi is a nonterminal) call procedure Xi(); elseif (Xi == current) advance input; else report error; } } • איך מבצעים את הבחירה של כלל הגזירה? • ניתן פשוט לנסות אותם אחד אחר השני ולהמשיך באופן רקורסיבי עם backtracking. אבל זה יכול להיות מאד יקר.

  33. איך מחליטים בין הכללים? • בדוגמה שראינו הספיק lookahead של אסימון יחיד. • פורמלית: • עבור כלל A→α, נגדיר: FIRST(α) – רשימת האסימונים שהם ראשונים באחת או יותר גזירות אפשריות הנובעות מכלל זה. • בדוגמה שלנו, עבור הכללים של E: FIRST(LIT) = { true, false } FIRST( ( E OP E ) ) = { ( } FIRST(not E) = { not } • אין שום חיתוך בין הקבוצות השונות ולכן ניתן מיד לדעת מה לגזור עם lookahead של אסימון יחיד.

  34. איך מחליטים בין הכללים? • אם יש חיתוך בין ה-FIRST-ים עבור כללים שונים של nonterminalנתון, צריך לתקן את הדקדוק או להשתמש ב-lookahead עמוק יותר. • מחלקת הדקדוקים שעבורם ניתן לקבוע את כלל הגזירה הבא בכל פעם ע"י lookahead של k אסימונים נקראת LL(k).

  35. ניתוח תחבירי (Parsing) של דקדוקי LL(1) המחלקה LL(1) היא המחלקה של דקדוקים שניתן לגזור עם look-ahead של אסימון אחד. יש דקדוקים כאלו לשפות תכנות רבות.

  36. דוגמא לבעיה הניתנת לפיתרון:רקורסיה שמאלית • רקורסיה שמאלית יוצרת בעיית זיהוי עבור ניתוח top-down שלא ניתן לפתור בעזרת lookahead חסום. • A -> AaB | aC • לא ניתן לדעת איזה מהכללים להפעיל. • (ויותר: backtracking עשוי להיתקע בלולאה אינסופית! ) • משנים את הדקדוק. • לכל דקדוק עם רקורסיה שמאלית יש דקדוק מקביל בלעדיה. • A -> aCA’A’ -> aBA’ | ε

  37. אלימינציה של רקורסיה שמאלית • ביטול רקורסיה ישירה: נחליף את הכללים • A → Aα1 | Aα2 | ··· | Aαn | β1 | β2 | ··· | βn • בכללים • A → β1A’ | β2A’ | ··· | βnA’ • A’ → α1A’ | α2A’| ··· | αnA’ | Є • אבל יש גם רקורסיה עקיפה. למשל: • S → Aa | b • A → Ac | Sd | Є • ועבורה האלגוריתם מעט יותר מורכב.

  38. אלגוריתם להעלמת רקורסיה שמאלת (עקיפה וישירה) מדקדוק • קלט:דקדוק G שאולי יש בו רקורסיה שמאלית, ללא מעגלים, וללא כללי ε. • פלט:דקדוק שקול ללא רקורסיה שמאלית. • דוגמא לכלל אפסילון: A → Є. • דוגמא למעגל: A → B; B → A;. • ניתן לבטל כללי אפסילון ומעגלים בדקדוק (באופן אוטומטי). • רעיון האלגוריתם לסילוק רקורסיה שמאלית:נסדר את המשתנים לפי סדר כלשהו:A1, A2, …, An • נעבור על המשתנים לפי הסדר, ולכל Ai נדאג שכל כלל שלו יהיה מהצורה • Ai→ Ajβ with j > i . • מדוע זה מספיק?

  39. אלגוריתם להעלמת רקורסיה שמאלת (עקיפה וישירה) מדקדוק • Input: Grammar G possibly left-recursive, no cycles, no ε productions. • Output: An equivalent grammar with no left-recursion • Method: Arrange the nonterminals in some order A1, A2, …, An • for i:=1 to n do begin for s:=1 to i-1 do begin replace each production of the form Ai → Asβ by the productions Ai→ d1β |d2β|…|dkβ where As-> d1 | d2 | …| dk are all the current As-productions; end eliminate immediate left recursion among the Ai-productionsend

  40. ניתוח האלגוריתם • נראה שבסיום האלגוריתם כל חוק גזירה מהצורה Ak→Atβ מקיים t > k . • שמורה 1: כשגומרים את הלולאה הפנימית עבור s כלשהו (עם Ai בלולאה החיצונית) אז כל כללי הגזירה של Ai מתחילים בטרמינלים, או במשתנים Aj עבורם j>s. • שמורה 2:כשמסיימים עם המשתנה Ai, כל כללי הגזירה שלו מתחילים במשתנים Aj עבורם j>i או בטרמינלים. הוכחת שתי השמורות יחד באינדוקציה על i ו-s. • מסקנה:בסיום האלגוריתם אין רקורסיה שמאלית בין המשתנים המקוריים (ישירה או עקיפה). נובע משמורה 2. • לגבי המשתנים החדשים, הם תמיד מופיעים כימניים ביותר, ולכן לעולם לא יהיו מעורבים ברקורסיה שמאלית.

  41. הערות • מדוע לא עובד אם יש כלל ε? • כי אם יש:A5→ A4A3 אז אנו דואגים שה-A4 ייעלם מהתחלת הכלל ויוחלף, למשל ב-A6, ואז יכול להתקבל A5→ A6A3. אבל אם A6→ε, אז בעצם ניתן לגזור A5→ A3 ואז השמורות לא תקיפות ועלולים לקבל רקורסיה שמאלית עקיפה. בעיקרון, טיפלנו רק במשתנה השמאלי ואסור לו להיעלם!

  42. הקטנת הצורך ב-lookahead בעזרת Left Factoring • הקבוצה first עבור כלל גזירה Ai→ β מכילה את קבוצת הטרמינלים שעשויים להופיע ראשונים בגזירה כלשהי של β. • בעיה נוספת של Recursive Descent היא התנגשויות ב-FIRST של כללי גזירה שונים לאותו משתנה. • הפתרון: Left Factoring – פירוק שמאלי, אלגוריתם המפיק דקדוק חלופי ללא הבעיה. • אינטואיציה:כאשר לא ברור כרגע איך להחליט בין שני כללים, ניצור כלל משותף לתחילת הגזירה ונפריד לשני כללים בהמשך. למשל:

  43. אלגוריתםLeft Factoring • Input: Grammar G • Output: An equivalent left-factored grammar • Method: For each nonterminal A find the longest (non empty) prefix α common to two or more of its production rules. Namely: A →α b1 | α b2 | …| α bn. Replace all the A productions A →α b1 | α b2 | …| αbnby A →α A’ A’→ b1 | b2 | … | bn (A’ is a new nonterminal) • Repeatedly apply this transformation until no such common prefix exists.

  44. עוד טרנספורמציות ? • קיימות עוד טרנספורמציות שמטרתן לייצר דקדוק ללא התנגשויות ב-FIRST. • הטרנספורמציות הללו מאפשרות גזירת top-down של שפות רבות. • אפשר לגזור כל דקדוק שעבר "טיפול" כזה בהצלחה בעזרת Recursive Descent. • ישנן תכונות של שפות שלא ניתנות לזיהוי ע"י שום דקדוק חסר הקשר. למשל, הדרישה של C ו-Java שכל משתנה יוגדר לפני השימוש בו. • w2w | w is in (0|1)* אבסטרקציה של הבעיה: • בדיקת דרישות כאלו תיכלל בניתוח הסמנטי.

  45. האם בשבוע הבא תתקיים הרצאה ? • לא.

  46. אלגוריתם LL(1)

  47. מחלקת הדקדוקים LL(k) • דקדוק הוא במחלקה LL(k) אם הוא ניתן לגזירה: • top-down, • סורקת את הקלט משמאל (L) לימין, • מניבה את הגזירה השמאלית (L) ביותר, • וזקוקה ל-lookahead בגודל k. • שפה היא LL(k)אם יש לה דקדוק LL(k). • המקרה הפשוט ביותר הוא אלגוריתם LL(1).

  48. אלגוריתם הגזירה • דיברנו על מציאת גזירה למילה בדקדוק LL(1) באמצעות Recursive Descent שהוא אלגוריתם כללי לגזירת top-down. • חסרונות: • משתמש ברקורסיה, • תכונות הדקדוק מתוארות באריכות באמצעות קוד. • נרצה טבלה המתארת את הגזירה, ונסלק רקורסיה. • סילוק רקורסיה חוסך מקום ומאפשר לטפל בהתפוצצות המחסנית של זמן הריצה. הרקורסיה מוחלפת במחסנית המכילה את התבנית שנותר לגזור. • אלגוריתמים מבוססי-טבלה לניתוח שפות LL(k) ידועים בשם LL(k) parsers.

  49. טבלת המעברים • טבלאות LL(1)תתארנה עבור כל מצב נתון, באיזה כלל גזירה להשתמש. • שורות הטבלה: משתנים. • עמודות הטבלה: אסימונים אפשריים בקלט. • תוכן הטבלה: חוקי גזירה.

  50. למשל... (1) E → LIT (2) E → ( E OP E ) (3) E → not E (4) LIT → true (5) LIT → false (6) OP → and (7) OP → or (8) OP → xor

More Related