1 / 27

תורת הקומפילציה 236360 הרצאה 4 ניתוח תחבירי ( Parsing ) של דקדוקי LL(1)

תורת הקומפילציה 236360 הרצאה 4 ניתוח תחבירי ( Parsing ) של דקדוקי LL(1). Wilhelm, and Maurer – Chapter 8 Aho, Sethi, and Ullman – Chapter 4 Cooper and Torczon – Chapter 3. תזכורת: front-end שלב הניתוח. תוכנית מקור Back end. Lexical Analysis. token string. syntax analysis Parsing.

aliya
Download Presentation

תורת הקומפילציה 236360 הרצאה 4 ניתוח תחבירי ( Parsing ) של דקדוקי LL(1)

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הרצאה 4 ניתוח תחבירי (Parsing) של דקדוקי LL(1) Wilhelm, and Maurer – Chapter 8 Aho, Sethi, and Ullman – Chapter 4 Cooper and Torczon – Chapter 3

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

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

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

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

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

  7. התאמת הדקדוק ל-Recursive Descent • לא כל דקדוק ניתן לגזירה יעילה top-down. כאשר הדקדוק לא מתאים מנסים לתקן אותו. • בעיה לדוגמא: רקורסיה שמאלית. • ביטול רקורסיה ישירה: נחליף את הכללים • A → Aα1 | Aα2 | ··· | Aαn | β1 | β2 | ··· | βn • בכללים • A → β1A’ | β2A’ | ··· | βnA’ • A’ → α1A’ | α2A’| ··· | αnA’ | Є • אבל יש גם רקורסיה עקיפה. למשל: • S → Aa | b • A → Ac | Sd | Є • ועבורה האלגוריתם מעט יותר מורכב.

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

  9. אלגוריתם להעלמת רקורסיה שמאלת (עקיפה וישירה) מדקדוק • 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

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

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

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

  13. אלגוריתםLeft Factoring • Input: Grammar G • Output: An equivalent left-factored grammar • Method: For each nonterminal A find the longest (non empty) prefix a 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.

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

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

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

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

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

  19. למשל... (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

  20. אלגוריתם LL(1) משתמש בטבלה ומחסנית נשתמש במחסנית לשמור את התבנית הפסוקית שעוד נותר לגזור. a * c + b $ קלט Parser טבלת מעברים מחסנית פלט

  21. מבנה נתונים בזמן ריצת האלגוריתם: מחסנית: if ( E ) then Stmt else Stmt ; Stmt ; … $ top Remaining Input: if ( id < id ) then id = id + num else break; id = id * id; …

  22. האלגוריתם • אתחול המחסנית: המשתנה (nonterminal) התחילי, ו-$ (סימן לסוף הקלט). • המחסנית יכולה להכיל אסימונים (terminals) או משתנים. "$" הוא אסימון מיוחד, לצורך סימון סוף הקלט. • אם בראש המחסנית יש אסימון: • אם האסימון הבא בקלט אינו זהה: שגיאה. • אם הוא תואם את הקלט: עבור לאסימון הקלט הבא; הסר את האסימון מהמחסנית. (אם האסימון הוא $, סיימנו). • אם בראש המחסנית יש משתנה: • מצא את התא בטבלה המתאים למשתנה זה ולתו שבראש הקלט. • אם התא ריק:שגיאה. • אחרת:הסר את המשתנה מראש המחסנית; הוסף למחסנית את צד ימין של כלל הגזירה שנמצא בטבלה, לפי סדר – החל באסימון/משתנה הימני ביותר וכלה באסימון/משתנה השמאלי ביותר (הוא ישאר בראש המחסנית).

  23. בניית הטבלה • בתרגול ראיתם איך בונים את First ו-follows, והם ישמשו לבניית הטבלה. • הבניה עצמה תפורט בתרגול.

  24. דוגמא טריביאלית (בתרגול דוגמאות רבות) דקדוק: A →aAb | c טבלה: • נריץ את האלגוריתם על המילה aacbb: • איתחול מחסנית: A$, ומתחילים מאות הקלט הראשונה.

  25. אלגוריתמים LL(k) • עבור k>1, הטבלה הנדרשת לאלגוריתם LL(k) היא (במקרה הגרוע) בעלת סיבוכיות אקספוננציאלית ב-k. • לכן, עד לא מזמן האמינו שלא יהיה מעשי לבנות parsers מעשיים לדקדוקי , LL(k) עבור k-ים יותר גדולים. • בתחילת שנות התשעים הדגימו חוקרים מאוניברסיטת Purdue (ארה"ב) שהמקרה הגרוע הוא למעשה נדיר, וניתן לבנות parsers פרקטיים עם LL(k). • הכלי שפיתחו נקרא כיום ANTLR. • כלים אחרים המבוססים על LL(k): JavaCC (משמש לבניית מהדרים ב-Java, כולל מהדר javac עצמו), SableCC (גם הוא ב-Java), ואחרים.

  26. LL(k) or not LL(k) ? • השפה an(b|c)nהיא שפה ב-(1)LL; "דקדוק טבעי":נבצע left-factoring ונקבל את הדקדוק:(*) מה לגבי השפות?anbn|ancn ?an(a|b)n • דקדוק/שפה שאינם LL(k): S1 → aS1b | aS1c | ε S1 → aS1X | ε X → b|c S → A | B A → aAb | c B → aBbb | d ancbn| andb2n

  27. LL(k) or not LL(k) ? S → akb | akc • דקדוק ב-LL(k+1) שאיננו ב-(LL(k:left-factoring) יניב דקדוק שקול ב-LL(1).) • דקדוק/שפה ב-LL(k+1) שאיננה ב-LL(k):רק כשרואים b או c יודעים שצריך להפעיל את המעבר של S ל-ε. S → aSA | ε A → akbS | c

More Related