הההה הההההההההה236360 ההההה2 ( ההההה ההההההParsing ) Wilhelm, and Maurer – Chapter 8 Aho, Sethi, and Ullman – Chapter 4 Cooper and Torczon – Chapter 3
Jan 21, 2016
236360תורת הקומפילציה 2הרצאה (Parsingניתוח תחבירי )
Wilhelm, and Maurer – Chapter 8Aho, Sethi, and Ullman – Chapter 4Cooper and Torczon – Chapter 3
בשבוע הבא לא תתקיים הרצאה
במילים אחרות, אין צורך להגיע בבוקר יום חמישי הבא. 2לאודיטוריום
front-endשלב הניתוח תוכנית מקור
Back end
Lexical Analysis
syntax analysisParsing
semantic analysis
token string
parse tree
decorated syntax tree
symboltable
errormessages
תוכנית מקור
Lexical analysis
parser
tokenget next token
parserהאינטרקציה בין המנתח לקסיקלי וה-
errormessagemanager
ניתוח סינטקטי
להבין את המבנה של התוכנית.המטרה:
בנויה מפונקציות, כל פונקציה בנויה Cלמשל: תוכנית מהצהרות ופקודות, כל פקודה בנויה מביטויים וכו'.
צורה פשוטה אך מדויקת לניסוח מבנה של תוכנית )בשפת תוכנית ספציפית( היא ע"י דקדוק חסר הקשר.
אנו נתעניין במחלקות של דקדוקים ח"ה שניתן לנתח ביעילות.
...פרטים בהמשך
, יוודא שהם מקיימים tokens יקרא את רצף ה-parserה-את הדקדוק )או יתריע על שגיאות(, ויבנה את עץ הגזירה של
התוכנית.
דקדוק חסר הקשר
G=(V, T, P, S)V – nonterminalsמשתנים ,
T – terminals ,טרמינלים ,tokens
Pחוקי גזירה –
P = V (V U T)*
Sמשתנה תחילי –
S V
דקדוק ח"ה עבור ביטוי אריתמטידוגמא:
א: ל' ימּון מ) ס*
סימון מקוצר:
{E, A} V =
{ ) , ( , - , id , +, - , , / , ^} T =
A +, , { E E A E
P =
A ‒, , E ) E (
A , , E - E
A ^} A /, , E id
E S =
E A E | ) E ( | - E | id E
+ | ‒ | | / | ^ A
שפה חסרת הקשר
– סדרה של החלפות של אותיות לא טרמינליות תוך שימוש בחוקי גזירההגזירה
– אוסף ביטויים הנגזרים מהמצב התחילי והמכילים טרמינלים שפהבלבד
E A E E
id A E
id + E
id + id
סימון הגזירה
שפה חסרת הקשר
– סדרה של החלפות של אותיות לא טרמינליות תוך שימוש בחוקי גזירההגזירה
– אוסף ביטויים הנגזרים מהמצב התחילי והמכילים טרמינלים שפהבלבד
– תוצאת סדרת גזירות בה נותרו (sentential formתבנית פסוקית ))אולי( לא-טרמינלים
E * id + E
– גזירה בה מוחלף בכל שלב הסימן השמאלי ביותר גזירה שמאלית)באופן דומה – גזירה ימנית(
ניתן לגזירה (רב שלבית)
E
E EA
E
id
E A E
E
E A E
E
id +
E A E
E
id + id
bottom-upדוגמא לגזירה
נתון דקדוקS → a A c B eA → A b | bB → 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 B אלטרנטיבות 3
A
B
S
A B אלטרנטיבות 3
bottom-upדוגמא לגזירה
נתון דקדוקS → a A c B eA → A b | bB → 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 B אלטרנטיבות 3
A
B
S
A B אלטרנטיבות 3
bottom-upדוגמא לגזירה
נתון דקדוקS → a A c B eA → A b | bB → 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 B אלטרנטיבות 3
A
B
S
A B אלטרנטיבות 3
bottom-upדוגמא לגזירה
נתון דקדוקS → a A c B eA → A b | bB → 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 B אלטרנטיבות 3
A
B
S
A B אלטרנטיבות 3
bottom-upדוגמא לגזירה
נתון דקדוקS → a A c B eA → A b | bB → 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 B אלטרנטיבות 3
A
B
S
A B אלטרנטיבות 3
האם קיבלנו גזירה ימנית או שמאלית?
קיבלנו גזירה ימנית
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ה-
ניתוחים שהולכים על הקלט משמאל לימין ומייצרים גזירה שמאלית . top-down והם עובדים לפי LLמסומנים ב-
ביטוי רגולרי מול דקדוק חסר הקשר
שפות חסרות הקשר יותר חזקות משפות רגולריות. בפרט, כל מבנה שניתן לבטא ע"י ביטוי רגולרי יכול להיות מתואר ע"י דקדוק.
ההיפך לא נכון )למשל?(
מדוע לא לעשות הכל בדקדוק? .כרגיל: הפרדה, מודולריות, פישוט אין טעם להפעיל כלים חזקים )ופחות יעילים( על ביטויים רגולריים
שקל לנתח.
, identifiersביטוים רגולרים שימושיים עבור תאור מבנים לקסיקלים כ- קבועים, מילות מפתח וכו'
דקדוקים שימושיים עבור מבנים מקוננים כסוגרים מאוזנים, התאמת begin-end, if-then-else'וכו ,
סוגי הניתוח התחבירי
כל דקדוק חסר-הקשר שקול לאוטומט מחסנית )אי-דטרמיניסטי(. .אבל – לא שמיש כי לא שקול לאוטומט מחסנית דטרמיניסטי
מתאים לגזירת כל Cocke-Younger-Kasami האלגוריתם באופן כללי:. אנו נעבוד עם אלגוריתמים לינאריים.O(n3)דקדוק אבל בסיבוכיות
...חישבו על תוכנית של מאות אלפי שורות קוד
המטרהפענוח ובנית עץ הגזירה תוך מעבר בודד על הקלט, משמאל לימין בכל שלב, הקלטw מתחלק ל x y
החלק שטרם נקרא חלק הקלט שקראנו
*
0
n
סוגי הניתוח התחבירי )המשך(
top-down – "מהשורש לעלים )נקרא גם – "ניתוח תחזית – predictive)
bottom-up מהעלים לשורש – מעבירים למחסנית, או מחליפים צד – (. shift reduceימין בסימן מהצד השמאלי של חוק הדקדוק )נקרא גם
*
0
n
x y
s
x y
s
top-downנתחיל בניתוח
נתונים לנו דקדוק ומילה.
רוצים להחליט ע"י מעבר על המילה, כיצד המשתנה התחילי S נגזר, ואח"כ תוך-כדי סריקת המילה, כיצד ממשיכים לגזור
כל משתנה עד שמגיעים לטרמינלים.
בדקדוק כללי תיתכן יותר מאפשרות אחת לגזור את המשתנה השמאלי ביותר לפי האות הבאה בקלט.
אנו נשתדל לעבוד עם דקדוקים שבהם תהיה רק אפשרות קטן כדי להחליט מהו כלל lookaheadאחת, אולי נצטרך
הגזירה המתאים הבא.
לנתחדקדוק שקל )מאד(
התבוננו בדקדוק:E → LIT | ( E OP E ) | not ELIT → true | falseOP → and | or | xor
בכל שלב של הגזירה, אם מסתכלים על משתנה שצריך לגזור והאות הבאה
בקלט, ברור מה כלל הגזירה שצריך להפעיל !
למשל איך נגזרת המילה not ( not true or false ) ?
E
E
E E
not
OP
LITnot
true
( )
or false
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 )
LIT
מתרגם דקדוק באופן הבא:Recursive Descentאלגוריתם .מטרה: להתחיל במשתנה התחילי ולמצוא גזירה( עבור כל משתנה בדקדוקnonterminal.מגדירים פונקציה )-המנתח מתחיל לפעול מהפונקציה המתאימה לnonterminal
התחילי.:כל פונקציה מחקה את החוק שעבורו הוגדרה, באופן הבא
terminal מתורגם לקריאת האסימון המתאים מהקלט ובדיקת התאמה.
nonterminal.מתורגם להפעלת הפונקציה המתאימה לו אם ישנם כמה חוקי גזירה עבור אותוnonterminal בוחרים ביניהם ,
.lookaheadבעזרת
תוך כדי הפעלת פונקציות: top-downניתוח Recursive Descent
matchפונקציית עזר:
void match(token t) {if ( current == t )
current = next_token();else
error;}
פונקציה זו משמשת כדי לקרוא טרמינלים ולבדוק התאמה.
מחזיק את התו )האסימון( שעליו currentהמשתנה מסתכלים כרגע בקלט.
כתיבת פונקציות בהתאם לדקדוק
למשל, עבור הדקדוק:
E → LIT | ( E OP E ) | not ELIT → true | falseOP → and | or | xor
OP ו-E, LITנגדיר שלוש פונקציות:
כתיבת פונקציות בהתאם לדקדוק
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;}
}
כתיבת פונקציות בהתאם לדקדוק
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;}
סך-הכל: כתיבת פונקציות בהתאם לדקדוק
E → LIT | ) E OP E ( | not ELIT → true | falseOP → and | or | xor
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;
}
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;
}
סך-הכל: כתיבת פונקציות בהתאם לדקדוק
E → LIT | ) E OP E ( | not ELIT → true | falseOP → and | or | xor
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;
}
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;
}
הפונקציות האלו נראות כאילו הן לא עושות כלום
(חוץ מלזהות שגיאות), אבל ניתן כמובן להוסיף לכל אחת קוד נוסף, למשל שבונה את העץ כך שיוחזר העץ המלא
של הגזירה ביציאה מן הרקורסיה.
הוספת פעולות במהלך הגזירה
)( E(), LITבכל פעם שנקראת אחת הפונקציות )למשל, )( בדוגמא שלנו(, פירוש הדבר ש"איתרנו" צעד בגזירה.OPו-
בכל צעד כזה ניתן לבצע פעולות שונות.
הפעולות האלו נקראות "פעולות סמנטיות" ונרחיב עליהן בשיעור נפרד.
בפרט, ניתן לבנות את עץ הגזירה: כל פונקציה תחזיר רשומה מסוגNode.)צומת בעץ( .כל רשומה כזו מכילה רשימה של בנים-או ל( בכל קריאה לפונקציה אחרתmatch מוסיפים את ,)
שנבנה כעת.Nodeתוצאת הקריאה ל-
הוספת פעולות במהלך הגזירה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 Eresult.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
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;
elsereport error;
}}
איך מבצעים את הבחירה של כלל הגזירה? •ניתן פשוט לנסות אותם אחד אחר השני ולהמשיך באופן רקורסיבי עם •
backtracking .אבל זה יכול להיות מאד יקר .
איך מחליטים בין הכללים?
של אסימון יחיד.lookaheadבדוגמה שראינו הספיק
פורמלית:
– רשימת האסימונים שהם ראשונים FIRST(α), נגדיר: A→αעבור כלל באחת או יותר גזירות אפשריות הנובעות מכלל זה.
:Eבדוגמה שלנו, עבור הכללים של FIRST(LIT) = { true, false }FIRST( ( E OP E ) ) = { ( }FIRST(not E) = { not }
אין שום חיתוך בין הקבוצות השונות ולכן ניתן מיד לדעת מה לגזור עם lookahead .של אסימון יחיד
איך מחליטים בין הכללים?
nonterminal-ים עבור כללים שונים של FIRSTאם יש חיתוך בין ה- עמוק יותר.lookahead להשתמש ב-אונתון, צריך לתקן את הדקדוק
מחלקת הדקדוקים שעבורם ניתן לקבוע את כלל הגזירה הבא בכל פעם . LL(k) אסימונים נקראת k של lookaheadע"י
( של דקדוקי Parsingניתוח תחבירי )LL)1(
היא המחלקה של דקדוקים שניתן LL(1)המחלקה של אסימון אחד. יש look-aheadלגזור עם
דקדוקים כאלו לשפות תכנות רבות.
רקורסיה דוגמא לבעיה הניתנת לפיתרון:שמאלית
שלא ניתן top-downרקורסיה שמאלית יוצרת בעיית זיהוי עבור ניתוח חסום. lookaheadלפתור בעזרת
A -> AaB | aC לא ניתן לדעת איזה מהכללים להפעיל.
עשוי להיתקע בלולאה אינסופית! (backtracking)ויותר: משנים את הדקדוק.
.לכל דקדוק עם רקורסיה שמאלית יש דקדוק מקביל בלעדיה
A -> aCA’A’ -> aBA’ | ε
אלימינציה של רקורסיה שמאלית
נחליף את הכללים ביטול רקורסיה ישירה:
A → Aα1 | Aα2 | ··· | Aαn | β1 | β2 | ··· | βn
בכללים
A → β1A’ | β2A’ | ··· | βnA’
A’ → α1A’ | α2A’| ··· | αnA’ | Є אבל יש גם רקורסיה עקיפה. למשל:
S → Aa | b
A → Ac | Sd | Є ועבורה האלגוריתם מעט יותר מורכב.
אלגוריתם להעלמת רקורסיה שמאלת )עקיפה וישירה( מדקדוק
שאולי יש בו רקורסיה שמאלית, ללא מעגלים, וללא כללי G דקדוק קלט:ε.
דקדוק שקול ללא רקורסיה שמאלית.פלט:
. A → Єדוגמא לכלל אפסילון:
;. A → B; B → A דוגמא למעגל: ניתן לבטל כללי אפסילון ומעגלים בדקדוק )באופן אוטומטי(.
רעיון האלגוריתם לסילוק רקורסיה שמאלית: נסדר את המשתנים לפי סדר A1, A2, …, Anכלשהו:
נדאג שכל כלל שלו יהיה מהצורה Aiנעבור על המשתנים לפי הסדר, ולכל
Ai → Ajβ with j > i .מדוע זה מספיק?
אלגוריתם להעלמת רקורסיה שמאלת )עקיפה וישירה( מדקדוק
Input: Grammar G possibly left-recursive, no cycles, no ε productions.Output: An equivalent grammar with no left-recursionMethod: 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
ניתוח האלגוריתם
< t מקיים Ak → Atβנראה שבסיום האלגוריתם כל חוק גזירה מהצורה k.
Ai כלשהו )עם s: כשגומרים את הלולאה הפנימית עבור 1שמורה מתחילים בטרמינלים, או Aiבלולאה החיצונית( אז כל כללי הגזירה של
. j>s עבורם Ajבמשתנים
, כל כללי הגזירה שלו מתחילים Ai: כשמסיימים עם המשתנה 2שמורה או בטרמינלים. j>i עבורם Ajבמשתנים
. s ו-iהוכחת שתי השמורות יחד באינדוקציה על
: בסיום האלגוריתם אין רקורסיה שמאלית בין המשתנים מסקנההמקוריים )ישירה או עקיפה(.
. 2נובע משמורה
לגבי המשתנים החדשים, הם תמיד מופיעים כימניים ביותר, ולכן לעולם לא יהיו מעורבים ברקורסיה שמאלית.
הערות
?εמדוע לא עובד אם יש כלל
ייעלם מהתחלת הכלל A4 אז אנו דואגים שה-A5 → A4A3 כי אם יש:. A5 → A6A3, ואז יכול להתקבל A6ויוחלף, למשל ב-
ואז השמורות לא תקיפות A5 → A3, אז בעצם ניתן לגזור A6 → εאבל אם ועלולים לקבל רקורסיה שמאלית עקיפה.
בעיקרון, טיפלנו רק במשתנה השמאלי ואסור לו להיעלם!
Left בעזרת lookaheadהקטנת הצורך ב-Factoring
מכילה את קבוצת הטרמינלים Ai → β עבור כלל גזירה firstהקבוצה
. βשעשויים להופיע ראשונים בגזירה כלשהי של
של FIRST היא התנגשויות ב-Recursive Descentבעיה נוספת של כללי גזירה שונים לאותו משתנה.
, אלגוריתם המפיק דקדוק פירוק שמאלי – Left Factoringהפתרון: חלופי ללא הבעיה.
אינטואיציה: כאשר לא ברור כרגע איך להחליט בין שני כללים, ניצור כלל משותף לתחילת הגזירה ונפריד לשני כללים בהמשך. למשל:
S → if E then S S’ | TS’→ else S | ε
S → if E then S else S | if E then S | T
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 | …| α bn
byA → α A’A’ → b1 | b2 | … | bn (A’ is a new nonterminal)
Repeatedly apply this transformation until no such common prefix exists.
עוד טרנספורמציות ?
קיימות עוד טרנספורמציות שמטרתן לייצר דקדוק ללא התנגשויות . FIRSTב-
של שפות רבות.top-downהטרנספורמציות הללו מאפשרות גזירת
Recursiveאפשר לגזור כל דקדוק שעבר "טיפול" כזה בהצלחה בעזרת Descent.
ישנן תכונות של שפות שלא ניתנות לזיהוי ע"י שום דקדוק חסר הקשר. שכל משתנה יוגדר לפני השימוש בו. Java ו-Cלמשל, הדרישה של
w2w | w is in (0|1)* אבסטרקציה של הבעיה: בדיקת דרישות כאלו תיכלל בניתוח הסמנטי.
האם בשבוע הבא תתקיים הרצאה ?
לא.
)LL)1אלגוריתם
LL(k)מחלקת הדקדוקים
אם הוא ניתן לגזירה:LL(k)דקדוק הוא במחלקה top-down,( סורקת את הקלט משמאלL,לימין )( מניבה את הגזירה השמאליתL,ביותר )-וזקוקה לlookahead בגודל k.
. LL(k)אם יש לה דקדוק LL(k)שפה היא
.LL(1)המקרה הפשוט ביותר הוא אלגוריתם
אלגוריתם הגזירה
Recursive באמצעות LL(1)דיברנו על מציאת גזירה למילה בדקדוק Descent שהוא אלגוריתם כללי לגזירת top-down.
חסרונות: ,משתמש ברקורסיה .תכונות הדקדוק מתוארות באריכות באמצעות קוד
נרצה טבלה המתארת את הגזירה, ונסלק רקורסיה. סילוק רקורסיה חוסך מקום ומאפשר לטפל בהתפוצצות המחסנית
של זמן הריצה. הרקורסיה מוחלפת במחסנית המכילה את התבנית שנותר לגזור.
LL(k) ידועים בשם LL(k)אלגוריתמים מבוססי-טבלה לניתוח שפות parsers.
טבלת המעברים
תתארנה עבור כל מצב נתון, באיזה כלל גזירה להשתמש.LL(1)טבלאות
שורות הטבלה: משתנים.
עמודות הטבלה: אסימונים אפשריים בקלט.
תוכן הטבלה: חוקי גזירה.
...למשל
$ xor or and false true not ( )
1 1 3 2 E
5 4 LIT
8 7 6 OP
(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(1)אלגוריתם
מחסנית
Parser
קלט
פלט
טבלת מעברים
נשתמש במחסנית לשמור את התבנית
הפסוקית שעוד נותר לגזור.
a * c + b $
מבנה נתונים בזמן ריצת האלגוריתם:
if ( E ) then Stmt else Stmt ; Stmts; } $
מחסנית:
top
if ( id < id ) then id = id + num else break ;id = id * id ;
…
Remaining Input:
גזירה בהינתן טבלה
( התחילי, ו-$ )סימן לסוף nonterminalאתחול המחסנית: המשתנה )הקלט(.
( המחסנית יכולה להכיל אסימוניםterminals או משתנים. "$" הוא )אסימון מיוחד, לצורך סימון סוף הקלט.
אם בראש המחסנית יש אסימון:.אם האסימון הבא בקלט אינו זהה: שגיאה אם הוא תואם את הקלט: עבור לאסימון הקלט הבא; הסר את
האסימון מהמחסנית. )אם האסימון הוא $, סיימנו(.אם בראש המחסנית יש משתנה:
.מצא את התא בטבלה המתאים למשתנה זה ולתו שבראש הקלט.אם התא ריק: שגיאה אחרת: הסר את המשתנה מראש המחסנית; הוסף למחסנית את צד
ימין של כלל הגזירה שנמצא בטבלה, לפי סדר – החל באסימון/משתנה הימני ביותר וכלה באסימון/משתנה השמאלי ביותר
)הוא ישאר בראש המחסנית(.
בניית הטבלה
, ובעזרתם את follows ו-Firstבתרגול תראו/ראיתם איך בונים את הטבלה.
דוגמא טריביאלית )בתרגול דוגמאות רבות(
דקדוק:A → aAb | c
a b c
A A → aAb (error) A → cטבלה:
:aacbbנריץ את האלגוריתם על המילה $, ומתחילים מאות הקלט הראשונה. Aאיתחול מחסנית: •
שארית הקלט המחסנית כלל מתאים
aacbb$ A$ A → aAb
aacbb$ aAb$ התאמת טרמינל
acbb$ Ab$ A → aAb
acbb$ aAbb$ התאמת טרמינל
cbb$ Abb$ A → c
cbb$ cbb$ התאמת טרמינל
…
LL(k)אלגוריתמים
היא )במקרה הגרוע( LL(k), הטבלה הנדרשת לאלגוריתם k>1עבור .kבעלת סיבוכיות אקספוננציאלית ב-
מעשיים parsersלכן, עד לא מזמן האמינו שלא יהיה מעשי לבנות -ים יותר גדולים. k עבור LL(k)לדקדוקי ,
)ארה"ב( Purdueבתחילת שנות התשעים הדגימו חוקרים מאוניברסיטת פרקטיים עם parsersשהמקרה הגרוע הוא למעשה נדיר, וניתן לבנות
LL(k). הכלי שפיתחו נקרא כיוםANTLR. כלים אחרים המבוססים עלLL(k): JavaCC משמש לבניית(
)גם הוא SableCC עצמו(, javac, כולל מהדר Javaמהדרים ב-(, ואחרים.Javaב-
LL(k) or not LL(k)?
; LL(1היא שפה ב-) an(b|c)nהשפה "דקדוק טבעי":
ונקבל את הדקדוק:left-factoringנבצע
anbn|ancn ?an(a|b)n)*( מה לגבי השפות?
:LL(k)דקדוק/שפה שאינם
S1 → aS1b | aS1c | ε
S1 → aS1X | εX → b|c
S → A | BA → aAb | c
B → aBbb | d ancbn | andb2n
LL(k) or not LL(k)?
:LL(k שאיננו ב-)LL(k+1) ב-דקדוק
left-factoring)-יניב דקדוק שקול ב LL(1)).
:LL(k) שאיננה ב-LL(k+1)דקדוק/שפה ב-
S יודעים שצריך להפעיל את המעבר של c או bרק כשרואים .ε ל-
S → aSA | ε A → akbS | c
S → akb | akc
מראה גזירה )לדוגמא( של תוכנית כללית
program
Main function More Functions
More FunctionsFunction
Function
Decls Stmts
Decls Stmts
Decls Stmts
• • •
• • • •
• •
• • •
Decl Decls
Decl Decls
Decl
Stmt Stmts
StmtIdType• • • •
• •
• • •
exprid =• • •
;
;
{ }
{
{
}
}
טיפול בשגיאות – נושא חשוב
סוגי שגיאות.)"שגיאות לקסיקליות )טעויות "דפוססוגרים לא מאוזנים(.שגיאות תחביריות( .)שגיאות סמנטיות )אופרנדים מטיפוס לא מתאים"="-במקום "=="(.שגיאות לוגיות )לולאה אינסופית, אבל גם שימוש ב
דרישות.דיווח על שגיאות בצורה ברורה היחלצות מהירה משגיאות כך שאפשר יהיה לגלות שגיאות המופיעות
בהמשך.-שימור יעילות הparser.
גישות לטיפול בשגיאות
ראשית, מודיעים איפה הניתוח נתקע. בד"כ זה די קרוב לשגיאה.
הבעיה העיקרית היא ההיחלצות מהשגיאה לצורך המשך ניתוח.
panic mode השמטת קטעים מהקלט עד מציאת סימן סנכרון ברור – )למשל ";" או סוגר סוגריים(.
.פשוט, אבל מאבד חלק )לפעמים משמעותי( מהקוד
phrase-level recovery "," נסיונות תיקון מקומיים. למשל החלפת – ב ";", הורדה או הוספה של ";", וכיו"ב.
.לא יעבוד אם הטעות קרתה לפני הזיהוי
error production טיפול בשגיאות צפויות ע"י תיקון אוטומטי במסגרת - הדקדוק.
global correction מציאת השינוי המינימאלי בתוכנית שיהפוך אותה – לגזירה. יקר, לא פרקטי.
parse generatorgrammar
tokens
parser
syntax tree
parsingמפורמליזם לתוכנה :
העיקרון דומה לזה של הניתוח הלקסיקאלי.
הפורמליזם והשיטות – שוניםהפורמליזם – דקדוק חסר הקשר-האמצעי – הרצת הparsing table ע"י recursive descent לכל .
מתאים נאמר בטבלה מה להפעיל. lookaheadמשתנה ו-
parsing tables
driver
parserparsing tables
לסיכום
לאחר שמקלפים את הקליפה הלקסיקלית, מפעילים ניתוח תחבירי parser-על ה tokens .להבנת התחביר - עץ התוכנית
מבנה תוכנית חוקית מתואר בפשטות ובדייקנות ע"י דקדוק חסר-הקשר.
-top או bottom-up( עובד בשיטת parserהמנתח התחבירי )down.ומגלה אם התוכנית נגזרת מהדקדוק ואיך
משתמשת בפונקציה המתאימה לכל recursive descentשיטת ה-. look-aheadמשתנה המחליטה על גזירת המשתנה עפ"י
ניתוח דקדוק כללי הוא קשה. ראינו דוגמא לדקדוק קל מאד לניתוח.
k של look-ahead בהינתן top-down ניתנים לגזירה LL(k)דקדוקי tokens .
באמצעות טבלה )במקום רקורסיה(. LL(1)ראינו גזירת
. bottom-upבשיעורים הבאים נדבר על גזירות