View
246
Download
5
Embed Size (px)
Citation preview
236360תורת הקומפילציה 3הרצאה (Parsingניתוח תחבירי )
Wilhelm, and Maurer – Chapter 8Aho, Sethi, and Ullman – Chapter 4Cooper and Torczon – Chapter 3
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
דקדוק ח"ה עבור ביטוי אריתמטי דוגמא:
Iן מHלGא: ימו Jס
סימון מקוצר:
V = {E, A}
T ={ ) , ( , - , id , +, - , , / , ^}
P =
{ E E A E
,A +,
E ) E (,A ‒,
E - E,A ,
E id,A /,A ^}
S =
E
E E A E | ) E ( | - E | id
A + | ‒ | | / | ^
שפה חסרת הקשר
– סדרה של החלפות של אותיות לא טרמינליות תוך שימוש גזירהבחוקי הגזירה
– אוסף ביטויים הנגזרים מהמצב התחילי והמכילים טרמינלים שפהבלבד
EE A 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'וכו ,
סוגי הניתוח התחבירי
כל דקדוק חסר-הקשר שקול לאוטומט מחסנית )אי-דטרמיניסטי(.)ונשתמש בו אז למה שלא נבנה אוטומט מחסנית )דטרמיניסטי
בתור מנתח תחבירי? מתאים לכל דקדוק אבל Cocke-Younger-Kasami באופן כללי:
ולכן האלגוריתם אינו מעשי.)O)n3בסיבוכיות המטרה
פענוח ובנית עץ הגזירה תוך מעבר בודד על הקלט, משמאל לימין בכל שלב, הקלטw מתחלק ל x y
החלק שטרם נקרא חלק הקלט שקראנו
סוגי הניתוח התחבירי )המשך(
top-down – "מהשורש לעלים )נקרא גם – "ניתוח תחזית – predictive)
bottom-up מהעלים לשורש – מעבירים למחסנית, או מחליפים צד – shiftימין בסימן מהצד השמאלי של חוק הדקדוק )נקרא גם
reduce .)
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 )lookahead == t(
lookahead = next_token)(;else
error;}
פונקצייה זו משמשת כדי לקרוא טרמינלים. הוא של אסימון אחד בלבד.lookaheadה-
כתיבת פונקציות בהתאם לדקדוק
למשל, עבור הדקדוק:E → LIT | ) E OP E ( | not ELIT → true | falseOP → and | or | xor
OP ו-E, LITנגדיר שלוש פונקציות:
כתיבת פונקציות בהתאם לדקדוק
void E)( {if )lookahead {TRUE, FALSE}( // E → LIT
LIT)(;else if )lookahead = LPAREN( // E → ) E OP E (
match)LPARENT(; E)(; OP)(; E)(; match)RPAREN(;else if )lookahead = NOT( // E → not E
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;}
הפונקציות האלו נראות כאילו הן לא עושות כלום
)חוץ מלזהות שגיאות(, אבל ניתן כמובן להוסיף לכל אחת קוד נוסף, למשל שבונה את העץ כך שיוחזר העץ המלא
של הגזירה ביציאה מן הרקורסיה.
הוספת פעולות במהלך הגזירה
)(LIT ,)(Eבכל פעם שנקראת אחת הפונקציות )למשל, בדוגמא שלנו(, פירוש הדבר ש"איתרנו" צעד )(OPו-
בגזירה.בכל צעד כזה ניתן לבצע פעולות שונות.
בפרט, ניתן לבנות את עץ הגזירה.
כל פונקציה תחזיר רשומה מסוגNode.)צומת בעץ( .כל רשומה כזו מכילה רשימה של בנים-או ל( בכל קריאה לפונקציה אחרתmatch מוסיפים ,)
שנבנה כעת.Nodeאת תוצאת הקריאה ל-
הוספת פעולות במהלך הגזירהNode E)( {
result = new Node)(; result.name = “E”;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 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 == lookahead(advance input;
elsereport error;
}}
איך מבצעים את הבחירה של כלל הגזירה?•ניתן פשוט לנסות אותם אחד אחר השני ולהמשיך באופן רקורסיבי עם •
backtracking .אבל זה יכול להיות מאד יקר .
איך מחליטים בין הכללים?
של אסימון יחיד.lookaheadבדוגמה שראינו הספיק פורמלית:
– רשימת האסימונים שהם ראשונים )FIRST)α, נגדיר: A→αעבור כלל באחת או יותר גזירות אפשריות הנובעות מכלל זה.
:Eבדוגמה שלנו, עבור הכללים ל-FIRST)LIT( = { true, false }FIRST) ) E OP E ( ( = { ) }FIRST)not E( = { not }
אין שום חיתוך בין הקבוצות השונות ולכן ניתן מיד לדעת מה לגזור עם lookahead .של אסימון יחיד
איך מחליטים בין הכללים?
נתון, nonterminal עבור כללים שונים ל-FIRSTsאם יש חיתוך בין ה- עמוק יותר.lookahead להשתמש ב-אוצריך לתקן את הדקדוק
מחלקת הדקדוקים שעבורם ניתן לקבוע את כלל הגזירה הבא בכל . )LL)k אסימונים נקראת k של lookaheadפעם ע"י
דוגמא לבעיה הניתנת לפיתרון
שלא ניתן top-downרקורסיה שמאלית יוצרת בעיית זיהוי עבור ניתוח חסום. lookaheadלפתור בעזרת
A -> AaB | aC לא ניתן לדעת איזה מהכללים להפעיל.
עשוי להיתקע בלולאה אינסופית! ( backtracking )ויותר:משנים את הדקדוק.
.לכל דקדוק עם רקורסיה שמאלית יש דקדוק מקביל בלעדיה
A -> aCA’A’ -> aBA’ | ε
נלמד את אלגוריתם האלימינציה של רקורסיה שמאלית.
של דקדוקים הנוחים )LL)1בשיעורים הבאים נלמד על המחלקה לגזירה מהירה, ומתאימים לשפות תכנות רבות.
מראה גזירה )לדוגמא( של תוכנית כללית
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משתנה המחליטה על גזירת המשתנה עפ"י
בעת הפעלת הפונקציות תוך כדי הניתוח ניתן כמובן לעשות דברים נוספים, כגון לשמור מידע על עץ הגזירה לשימוש עתידי וכיו"ב.
ניתוח דקדוק כללי הוא קשה. ראינו דוגמא לדקדוק קל מאד לניתוח. בשיעורים הבאים נדבר על מחלקות של דקדוקים ריאליסטיים שניתן
לנתח ביעילות.