( - המשךParsingניתוח תחבירי )
Wilhelm, and Maurer – Chapter 8Aho, Sethi, and Ullman – Chapter 4
התחבירי: הניתוח סוגי תזכורת
top-down – "מהשורש לעלים )נקרא גם – "ניתוח תחזית – predictive)
bottom-up מהעלים לשורש – מעבירים למחסנית, או מחליפים צד – (shift reduce)ימין בסימן מהצד השמאלי של חוק הדקדוק
x y
s
x y
s
מתרגם דקדוק לקוד באופן Recursive Descentאלגוריתם הבא:
עבור כלnonterminal.מגדירים פונקציה המנתח מתחיל לפעול מהפונקציה המתאימה
הראשון.nonterminalל-:כל פונקציה מחקה את החוק שעבורו הוגדרה, באופן הבא
terminal מתורגם לקריאת הלקסמה המתאימה מהקלט.
nonterminal מתורגם להפעלת הפונקציה המתאימה לו.
אם ישנם כמה חוקי גזירה עבור אותוnonterminal ,.lookaheadבוחרים ביניהם בעזרת
Recursive Descentתזכורת:
תזכורת: כתיבת פונקציות בהתאם לדקדוק
E → LIT | ( E OP E ) | not ELIT → true | falseOP → and | or | xor
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;
}
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;
}
בעיות: תזכורת
, או כללים המתחילים εאיך מתגברים על כללי-?εב-
, זו הופכת להיות ברירת FIRST ב-εאם יש המחדל.
מה קורה עם רקורסיה שמאלית?E → E A E | ( E ) | – E | idA → + | – | * | / | ^
מחליפים את הדקדוק. לכל דקדוק עם רקורסיה שמאלית מיידית יש
דקדוק שקול נטול רקורסיה שמאלית.
? לנו עוזר זה איך
פונקציה – עם קוד לנו יש יופי. גזירה כלל לכל מתאימה
הקלט לגזירת בהתאם מופעל הקוד. הנתון מהדקדוק
, אבל , למשל לקבל לנו עוזר זה איךגזירה ?עץ
הגזירה במהלך פעולות הוספת
בכל פעם שנקראת אחת הפונקציות בדוגמא ()OP ו-()E(), LIT(למשל,
שלנו), פירוש הדבר ש"איתרנו" צעד בגזירה.
בכל צעד כזה ניתן לבצע פעולות שונות!בפרט, ניתן די בקלות לבנות עץ בעזרת
הפעולות הללו.
הגזירה במהלך פעולות הוספת
דרך אחת לקבל עץ מהפונקציות הקיימות: כל פונקציה מחזירה רשומה מסוגNode
(צומת בעץ)..כל רשומה כזו מכילה רשימה של בנים בכל קריאה לפונקציה אחרת (או
), מוסיפים את תוצאת הקריאה matchל- שנבנה כעת.Nodeל-
הגזירה במהלך פעולות הוספת
Node E() {result = new Node();if (lookahead {TRUE, FALSE}) // E → LIT
result.addChild(LIT());else if (lookahead = LPAREN) // E → ( E OP E )
result.addChild(match(LPARENT));result.addChild(E()); result.addChild(OP()); result.addChild(E());result.addChild(match(RPAREN));
else if (lookahead = NOT) // E → not E
result.addChild(match(NOT)); result.addChild(E());else error;return result;
}
הגזירה במהלך פעולות הוספת
ואז, למשל:input = “(not true and false)”;Node treeRoot = E();
E
( E OP E )
not LIT
falsetrue
and LIT
הגזירה במהלך פעולות הוספת
כאמור, בפועל בד"כ לא באמת בונים עץ. ייצוג של התוכנית כולה בזכרון יכול
להיות מסובך ו"כבד".אבל באופן שקול, ניתן לבצע כל פעולה
שהיא בפונקציות השונות המתקבלות .RDבאלגוריתם
פעולות אלה תייצרנה, בסופו של דבר, .parserאת הפלט של ה-
RDהתאמת הדקדוק ל-
.RDלא כל דקדוק מתאים ל-.רקורסיה שמאליתהבעיה הקשה:
כזכור, קל לבטל רקורסיה שמאלית ישירה:
?רקורסיה שמאלית עקיפהמה לגבי S → Aa | b A → Ac | Sd | ε
A → Aα | β
A → βA’A’ → αA’ | ε
: אלגוריתם עקיפה שמאלית רקורסיה ביטול
ו/או לולאות.ε אם הדקדוק מכיל כללי עשוי לא לעבוד
Left Factoring בעזרת lookaheadהקטנת הצורך ב-
FIRST היא התנגשויות ב-RDבעיה נוספת של של כללי גזירה שונים לאותו משתנה.
, פירוק שמאלי – Left Factoringהפתרון: אלגוריתם המפיק דקדוק חלופי ללא הבעיה.
למשל:
S → if E then S else S | if E then S | T
S → if E then S S’ | TS’→ else S | ε
Left Factoringאלגוריתם
וזהו?
קיימות טרנספורמציות המייצרות דקדוק ללא FIRSTרקורסיה שמאלית וללא התנגשויות ב-
מדקדוקים רבים.אפשר לגזור כל דקדוק שעבר "טיפול" כזה
.RDבהצלחה בעזרת
מסקנה: אפשר לגזור שפות רבות ושונות .RDבעזרת
לשם מה אנו זקוקים לאלגוריתמים אחרים?
)LL)1 אלגוריתם
)LL)kאלגוריתם
הוא אלגוריתם:LL(k)אלגוריתם top-down,,מבוסס טבלה) סורק את הקלט משמאלL,לימין () מניב את הגזירה השמאליתL,ביותר (-וזקוק לlookahead בגודל k.
המקרה הפשוט ביותר הוא אלגוריתם LL(1).
)LL)kשפות
אם אפשר לגזור אותה LL(k)שפה נקראת .LL(k) parserבעזרת
k עבור LL(k) גוזר שפות RDאלגוריתם בלתי-חסום.
בעזרת LL(k)בדרך-כלל גוזרים שפות אלגוריתמים מבוססי-טבלה. אלגוריתמים אלו
.LL(k) parsersהם הידועים בשם
)LL)1אלגוריתם
מחסנית
Parser
קלט
פלט
מעברים טבלת
המעברים טבלת
משתמשים בטבלה המכתיבה, עבור LL(1)ב-כל מצב נתון, באיזה כלל גזירה להשתמש.
שורות הטבלה: כללי גזירה.עמודות הטבלה: אסימונים אפשריים בקלט.
תוכן הטבלה: חוקי גזירה.
למשל...
()nottrue
false
andorxor$
E2311
LIT45
OP678
(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
האלגוריתם
: המשתנה הראשון בדקדוק, ו-$ (סימן לסוף אתחול המחסניתהקלט).
המחסנית יכולה להכיל אסימונים או משתנים. "$" הוא אסימוןמיוחד, לצורך זה.
אם בראש המחסנית יש אסימון: :שגיאהאם האסימון הבא בקלט אינו זהה. אם הוא תואם את הקלט: צרוך את תו הקלט; הסר את האסימון
).סיימנומהמחסנית. (אם האסימון הוא $,
אם בראש המחסנית יש משתנה:.מצא את התא בטבלה המתאים למשתנה זה ולתו שבראש הקלט:שגיאה אם התא ריק.:הסר את המשתנה מראש המחסנית; הוסף למחסנית את אחרת
צד ימין של כלל הגזירה שנמצא בטבלה, לפי סדר – החל באסימון/משתנה הימני ביותר וכלה באסימון/משתנה השמאלי
ביותר (הוא ישאר בראש המחסנית).
הטבלה בניית
. בתרגול...
בטבלה- משמעויות ריבוי
שבטבלה לכך יגרמו מסוימים דקדוקים. יחיד מערך יותר עם תאים יהיו
? דקדוקים אילו? הבעיה על להתגבר ניתן כיצד
)LL)kאלגוריתמים
היא (במקרה LL(k), הטבלה הנדרשת לאלגוריתם k>1עבור .סיבוכיות אקספוננציאליתהגרוע) בעלת
parsersלכן, עד לא מזמן האמינו שלא יהיה מעשי לבנות לשפות תכנות "אמיתיות" בעזרת אלגוריתם זה.
לכןyacc, bison.וחברים מבוססים על אלגוריתמים אחרים Purdueבתחילת שנות התשעים הדגימו חוקרים מאוניברסיטת
ניתן לבנות (ארה"ב) שהמקרה הגרוע הוא למעשה נדיר, וparsers פרקטיים עם LL(k).
הכלי שפיתחו נקרא כיוםANTLR. כלים אחרים המבוססים עלLL(k): JavaCC משמש לבניית)
(גם SableCC עצמו), javac, כולל מהדר Javaמהדרים ב-), ואחרים.Javaהוא ב-