You are on page 1of 11

‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫‪CUP Parser Generator‬‬


‫ﺷﺎﻳﺎن ﻋﻠﻲاﻛﺒﺮ ﺗﺒﺮﻳﺰي‬
‫‪shayantabrizi@gmail.com‬‬
‫داﻧﺸﻜﺪهي ﺑﺮق و ﻛﺎﻣﭙﻴﻮﺗﺮ‬
‫داﻧﺸﮕﺎه ﺗﻬﺮان‬
‫ﺧﺮداد ‪1388‬‬

‫ﭼﻜﻴﺪه‬
‫اﻳﻦ ﻧﻮﺷﺘﻪ راﺟﻊﺑﻪ وﻳﮋﮔﻲﻫﺎ و ﻧﺤﻮهي اﺳﺘﻔﺎده از ‪ Constructor of Useful Parsers‬ﻳﺎ ﻫﻤـﺎن ‪ CUP‬ﻛـﻪ ﻣﺒﺘﻨـﻲ ﺑـﺮ‬
‫زﺑﺎن ﺟﺎوا اﺳﺖ‪ ،‬ﻣﻲﺑﺎﺷﺪ‪ CUP.‬اﺑﺰاري ﻗﺪرﺗﻤﻨﺪ ﺑﺮاي ﺗﻮﻟﻴﺪ ﺗﺠﺰﻳﻪﮔﺮ ‪ LALR‬از روي ﺗﻌﺮﻳﻒ ﮔﺮاﻣﺮ زﺑﺎن آن اﺳﺖ‪.‬‬
‫ﻛﻠﻴﺪواژهﻫﺎ‪ ،CUP :‬ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪﮔﺮ‪ LALR ،‬و ﺟﺎوا‬

‫‪ .1‬ﻣﻘﺪﻣﻪ‬

‫اﻳﻦ ﻧﻮﺷﺘﻪ راﺟﻊﺑﻪ وﻳﮋﮔﻲﻫﺎ و ﻧﺤﻮهي اﺳﺘﻔﺎده از ‪ Constructor of Useful Parsers‬ﻳﺎ ﻫﻤﺎن ‪ CUP‬ﻛﻪ ﻣﺒﺘﻨﻲ ﺑـﺮ زﺑـﺎن‬
‫ﺟﺎوا اﺳﺖ‪ ،‬ﻣﻲﺑﺎﺷﺪ‪.‬ﻛﺎپ اﺑﺰاري ﺑﺮاي ﺗﻮﻟﻴﺪ ﺗﺠﺰﻳﻪﮔﺮ ‪ LALR‬از روي ﮔﺮاﻣﺮ زﺑﺎن آن اﺳﺖ و ﺑﻴﺸﺘﺮ ﻛﺎرﻫـﺎي ﻧـﺮم اﻓـﺰار‬
‫ﻣﺸﻬﻮر ‪ YACC‬را اﻧﺠﺎم ﻣﻲدﻫﺪ‪ .‬وﻟﻲ ﻛﺎپ ﺑﻪ زﺑﺎن ﺟﺎوا اﺳﺖ و ﺗﺠﺰﻳﻪﮔﺮﻫﺎﻳﻲ ﺑﻪ زﺑﺎن ﺟﺎوا ﺗﻮﻟﻴﺪ ﻣﻲﻛﻨﺪ‪ .‬ﺑﺮاي اﺳـﺘﻔﺎده‬
‫از ﻛﺎپ ﺑﻪ ﺑﺮﻧﺎﻣﻪاي ﻧﻴﺰ ﺑﻪ ﻣﻨﻈﻮر اﻧﺠﺎم ﺗﺤﻠﻴﻞ ﻟﻐﻮي ﻧﻴﺎز اﺳﺖ ﻛﻪ اﺳﺘﻔﺎده از ‪ JLex‬ﺗﻮﺻﻴﻪ ﻣﻲﺷﻮد‪ .‬اﻳﻦ ﻣﻘﺎﻟﻪ در ‪ 5‬ﺑﺨـﺶ‬
‫ﺗﻨﻈﻴﻢ ﺷﺪه اﺳﺖ‪ .‬در ﺑﺨﺶ او‪‬ل ﺑﻪ آﺷﻨﺎﻳﻲ ﺑﺎ ﻛﺎپ و ﻣﺰاﻳﺎي آن ﻣﻲﭘﺮدازﻳﻢ‪ .‬در ﺑﺨـﺶ دوم ﺑـﻪ ﻧﺤـﻮهي اﺳـﺘﻔﺎده از ﻛـﺎپ‬
‫ﭘﺮداﺧﺘﻪ ﻣﻲﺷﻮد‪ .‬در ﺑﺨﺶ ﺳﻮم ﺳﺎﺧﺘﺎر ﻳﻚ ﺑﺮﻧﺎﻣﻪي ﻛﺎپ ﺷﺮح داده ﻣﻲﺷﻮد‪ .‬در ﺑﺨﺶ ﭼﻬﺎرم ﻧﺤﻮهي ﺑﺮﺧﻮرد ﺑـﺎ ﺧﻄـﺎ‬
‫ﺑﻴﺎن ﻣﻲﺷﻮد و در ﻧﻬﺎﻳﺖ در ﺑﺨﺶ آﺧﺮ ﺧﻼﺻﻪ‪ ،‬ﻧﺘﻴﺠﻪﮔﻴﺮي و ‪ ...‬اﻧﺠﺎم ﻣﻲﺷﻮد‪.‬‬

‫ﻣﺰاﻳﺎي اﺳﺘﻔﺎده از ﻛﺎپ‬


‫ﭘﺎره اي از ﻣﺰاﻳﺎي ﻛﺎپ ﻋﺒﺎرﺗﻨﺪ از‪:‬‬
‫‪ ‬ﻛﺎر ﺑﺎ آن ﺳﺎده اﺳﺖ‪.‬‬
‫‪ ‬ﻛﺎر ﺑﺎ آن ﺷﺒﻴﻪ ﻛﺎر ﺑﺎ ‪ YACC‬اﺳﺖ و ﺑﺮاي ﻛﺎرﺑﺮان ‪ YACC‬ﻣﻨﺎﺳﺐ اﺳﺖ‪.‬‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

.‫ﺗﻮﻟﻴﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﮔﺮاﻣﺮﻫﺎي ﻣﺒﻬﻢ را ﻧﻴﺰ اﻧﺠﺎم ﻣﻲ دﻫﺪ‬ 


.‫ را اﻧﺠﺎم ﻣﻲ دﻫﺪ‬Syntactic Error Recovery 
.‫ را اﻧﺠﺎم ﻣﻲ دﻫﺪ‬Syntactic Error Recovery 
.(... ‫ و‬GPL)‫ ﻫﺎي ﭘﺸﺘﻴﺒﺎﻧﻲ آن ﻧﻴﺎزﻣﻨﺪ ﺑﻪ ﻣﺠﻮز ﺧﺎﺻﻲ ﻧﻴﺴﺘﻨﺪ‬library ‫ﺗﺠﺰﻳﻪ ﮔﺮ ﺣﺎﺻﻞ و‬ 

‫ ﻧﺤﻮهي اﺳﺘﻔﺎده‬.2

‫ﺑﻪ ﻋﻨﻮان ﻣﺜﺎﻟﻲ ﺑﺮاي آﺷﻨﺎﻳﻲ ﺑﺎ زﺑﺎن ﻛﺎپ ﺑﻪ ﻣﺜﺎل ﻛﻪ زﻳﺮ ﻛﻪ ﺑﺮاي ﻣﺤﺎﺳﺒﻪي ﻣﻘﺪار ﻋﺪدي ﻳﻚ راﺑﻄـﻪي رﻳﺎﺿـﻲ ﺑـﺮ روي‬
:‫اﻋﺪاد ﺻﺤﻴﺢ ﺑﻪ ﻛﺎر ﻣﻲرود ﺗﻮﺟﻪ ﻛﻨﻴﺪ‬

// CUP specification for a simple expression evaluator (no actions)


import java_cup.runtime.*;
/* Preliminaries to set up and use the scanner. */
init with {: scanner.init(); :};
scan with {: return scanner.next_token(); :};
/* Terminals (tokens returned by the scanner). */
terminal SEMI, PLUS, MINUS, TIMES, DIVIDE, MOD;
terminal UMINUS, LPAREN, RPAREN;
terminal Integer NUMBER;
/* Non terminals */
non terminal expr_list, expr_part;
non terminal Integer expr, term, factor;
/* Precedences */
precedence left PLUS, MINUS;
precedence left TIMES, DIVIDE, MOD;
precedence left UMINUS;
/* The grammar */
expr_list ::= expr_list expr_part |
expr_part;
expr_part ::= expr SEMI;
expr ::= expr PLUS expr
| expr MINUS expr
| expr TIMES expr
| expr DIVIDE expr
| expr MOD expr
| MINUS expr %prec UMINUS
| LPAREN expr RPAREN
| NUMBER
;

. (‫ ﻣﺜﺎﻟﻲ ﺳﺎده ﺑﺮاي ﻛﺎر ﺑﺎ ﻛﺎپ)ﺑﺪون دﺳﺘﻮرات ﻣﻌﻨﺎﻳﻲ‬.1 ‫ﺑﺮﻧﺎﻣﻪي‬

‫ ﺳـﭙﺲ ﺗﻌﺮﻳـﻒ ﻛـﺮدن ﭘﺎﻳﺎﻧـﻪﻫـﺎ و‬.‫ﻫﻤﺎﻧﻄﻮر ﻛﻪ ﻣﻼﺣﻈﻪ ﻣﻲﺷﻮد اوﻟﻴﻦ ﮔﺎم ﻓﺮاﻫﻢ ﻛﺮدن ﻣﻘﺪﻣﺎت ﻻزم ﺑﺮاي ﺑﺮﻧﺎﻣﻪ اﺳـﺖ‬
‫ ﻧﺴﺨﻪﻫﺎي ﻗـﺪﻳﻤﻲ‬.‫ اﮔﺮ ﺗﻮﺟﻪ ﻛﺮده ﺑﺎﺷﻴﺪ ﮔﺮاﻣﺮ ﺑﺎﻻ ﮔﺮاﻣﺮي ﻣﺒﻬﻢ اﺳﺖ‬.‫ﻧﺎﭘﺎﻳﺎﻧﻪﻫﺎ و ﺳﭙﺲ ﺗﻌﺮﻳﻒ ﻛﺮدن ﮔﺮاﻣﺮ ﻣﻮرد ﻧﻈﺮ‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫ﻛﺎپ ﺑﺮﻧﺎﻣﻪ ﻧﻮﻳﺲ را ﻣﺠﺒﻮر ﻣﻲﻛﺮدﻧﺪ ﻛﻪ از ﮔﺮاﻣﺮﻫﺎي ﻏﻴﺮﻣﺒﻬﻢ اﺳﺘﻔﺎده ﻛﻨﺪ اﻣﺎ در ﻧﺴﺨﻪﻫﺎي ﺟﺪﻳـﺪ اﻣﻜـﺎن ﭘﺸـﺘﻴﺒﺎﻧﻲ از‬
‫اﻳﻦ ﮔﺮاﻣﺮﻫﺎ ﻫﻢ وﺟﻮد دارد و از ﻃﺮﻳﻖ ﺗﻌﺮﻳﻒ ﻛﺮدن اوﻟﻮﻳﺘﻬﺎ و ﺷﺮﻛﺖ ﭘﺬﻳﺮيﻫﺎ‪ ،‬اﺑﻬﺎمﻫﺎ رﻓﻊ ﻣﻲﺷﻮﻧﺪ‪.‬‬

‫ﺑﺮاي ﺳﺎﺧﺘﻦ ﺗﺠﺰﻳﻪ ﮔﺮ از ﻃﺮﻳﻖ ﻛﺎپ راﺣﺖﺗﺮﻳﻦ راه اﺳﺘﻔﺎده از ﻛﺘﺎﺑﺨﺎﻧﻪي ﺟﺎواي آن اﺳﺖ ﻛﻪ ﺑﻪ ﺻـﻮرت زﻳـﺮ‬
‫اﺳﺘﻔﺎده ﻣﻲﺷﻮد‪:‬‬
‫‪java -jar java-cup-xxx.jar file.cup‬‬

‫ﺗﻌﺮﻳﻒ ﮔﺮاﻣﺮ در ﻓﺎﻳﻞ ‪ file.cup‬ﺑﻪ ﻛﺎپ داده ﻣﻲﺷـﻮد و دو ﻓﺎﻳـﻞ ‪ Parser.java‬و ‪ sym.java‬ﺳـﺎﺧﺘﻪ ﻣـﻲﺷـﻮد‪ .‬ﺑـﺮاي‬
‫اﺳﺘﻔﺎده ﺑﺎﻳﺪ آﻧﻬﺎ را ﻫﻤﺮاه ﺑﺎ ‪ java-cup-xxx-runtime.jar‬ﺑﻪ ‪ java‬داد ﺗﺎ ﺑﺮﻧﺎﻣﻪ ﻣﻮرد ﻧﻈﺮ را اﺟﺮا ﻛﻨﺪ و ﺳﭙﺲ از ﻃﺮﻳﻖ‬
‫ورودي اﺳــﺘﺎﻧﺪارد ورودي را ﺑــﻪ آن داد‪ .‬دو ﻓﺎﻳــﻞ ‪ sym.java‬و ‪ parser.java‬ﺗﻌﺮﻳــﻒ دو ﻛــﻼس ‪ sym‬و ‪ parser‬را‬
‫درﺑﺮدارﻧﺪ‪ .‬ﻛﻼس ‪ sym‬ﺣﺎوي ﺗﻌﺮﻳﻒ ﻳﻚ ﺳﺮي ﺛﺎﺑﺖﻫﺎﺳﺖ ﻛﻪ ﺑﻪ ازاي ﻫﺮ ﭘﺎﻳﺎﻧﻪ ﻳـﻚ ﻣﻘـﺪار درون آن ﻗـﺮار دارد‪ .‬اﻳـﻦ‬
‫ﺗﻌﺎرﻳﻒ ﺑﻪ ﻃﻮر ﻋﻤﺪه ﺗﻮﺳﻂ ‪ scanner‬اﺳﺘﻔﺎده ﻣﻲﺷﻮﻧﺪ و اﺟﺮاي دﺳﺘﻮراﺗﻲ ﻣﺜﻞ‪:‬‬
‫;)‪return new Symbol(sym.SEMI‬‬
‫را ﻣﻤﻜﻦ ﻣﻲﺳﺎزﻧﺪ‪ .‬ﻛﻼس ‪ scanner‬ﻧﻴﺰ ﺗﻌﺮﻳﻒ ﺧﻮد ﺗﺠﺰﻳﻪﮔﺮ را در ﺑﺮ دارد‪.‬‬

‫ﺑﺮﻧﺎﻣﻪاي ﺑﻪ ﺷﻜﻞ ﻓﻮق ﺑﻪ ﻋﻨﻮان ﺧﺮوﺟﻲ ﺗﻨﻬﺎ ﻣﻮﻓﻘﻴﺖآﻣﻴﺰ ﺑﻮدن ﻳﺎ ﻧﺒﻮدن ﻧﺘﻴﺠﻪي ﺗﺠﺰﻳﻪ را ﺑﺮﻣـﻲﮔﺮداﻧـﺪ‪ .‬ﺑـﺮاي‬
‫اﻳﻨﻜﻪ ﻣﺤﺎﺳﺒﻪ ﻣﻘﺎدﻳﺮ اﻧﺠﺎم ﺷﻮد ﺑﺎﻳﺪ ﻗﻮاﻋﺪ ﻣﻌﻨﺎﻳﻲ را از ﻃﺮﻳﻖ ﻛﺪ ﺟﺎوا ﺑﻪ درون ﺗﻌﺮﻳﻒ ﮔﺮاﻣﺮ وارد ﻛﺮد‪ .‬اﻳﻦ ﻛﺪﻫﺎ ﻛﻪ در‬
‫ﻛﺎپ از آﻧﻬﺎ ﺗﺤﺖ ﻋﻨﻮان ‪ actions‬ﻳﺎد ﻣﻲﺷﻮد ﺑﻴﻦ ﺟﺪاﻛﻨﻨﺪهﻫﺎﻳﻲ ﺑﻪ ﺷﻜﻞ }‪ :‬و ‪ {:‬آورده ﻣﻲﺷﻮﻧﺪ‪ .‬اﻳﻦ ﻛﺪﻫﺎ ﻣﺴﺘﻘﻴﻤﺎ ﺑـﻪ‬
‫ﻛﺪ ﺗﺠﺰﻳﻪﮔﺮ ﻣﻨﺘﻘﻞ ﻣﻲﺷﻮﻧﺪ و ﺑﺮرﺳﻲ اﻳﻨﻜﻪ آﻳﺎ ﻛﺪ ﻣﻌﺘﺒﺮي ﻫﺴﺘﻨﺪ ﻧﻴﺰ اﻧﺠﺎم ﻧﻤﻲﺷﻮد‪ .‬ﻧﻤﻮﻧﻪي ﻛﺎﻣﻞﺗﺮي از ﻣﺜﺎل ﻓﻮق ﻛـﻪ‬
‫ﻗﻮاﻋﺪ ﻧﺤﻮي ﻧﻴﺰ در آن ﻧﻮﺷﺘﻪ ﺷﺪه اﺳﺖ در زﻳﺮ آﻣﺪه اﺳﺖ‪:‬‬

‫)‪// CUP specification for a simple expression evaluator (with actions‬‬


‫;*‪import java_cup.runtime.‬‬
‫‪/* Preliminaries to set up and use the scanner. */‬‬
‫;)(‪init with {: scanner.init‬‬ ‫;}‪:‬‬
‫;}‪scan with {: return scanner.next_token(); :‬‬
‫‪/* Terminals (tokens returned by the scanner). */‬‬
‫‪terminal‬‬ ‫;‪SEMI, PLUS, MINUS, TIMES, DIVIDE, MOD‬‬
‫‪terminal‬‬ ‫;‪UMINUS, LPAREN, RPAREN‬‬
‫;‪terminal Integer NUMBER‬‬
‫‪/* Non-terminals */‬‬
‫‪non terminal‬‬ ‫;‪expr_list, expr_part‬‬
‫;‪non terminal Integer expr‬‬
‫‪/* Precedences */‬‬
‫;‪precedence left PLUS, MINUS‬‬
‫;‪precedence left TIMES, DIVIDE, MOD‬‬
‫;‪precedence left UMINUS‬‬
‫‪/* The grammar */‬‬
‫‪expr_list ::= expr_list expr_part‬‬
‫|‬
‫;‪expr_part‬‬
‫‪expr_part ::= expr:e‬‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫}‪{: System.out.println("= " + e); :‬‬


‫‪SEMI‬‬
‫;‬
‫‪expr‬‬ ‫‪::= expr:e1 PLUS expr:e2‬‬
‫}‪{: RESULT = new Integer(e1.intValue() + e2.intValue()); :‬‬
‫|‬
‫‪expr:e1 MINUS expr:e2‬‬
‫}‪{: RESULT = new Integer(e1.intValue() - e2.intValue()); :‬‬
‫|‬
‫‪expr:e1 TIMES expr:e2‬‬
‫}‪{: RESULT = new Integer(e1.intValue() * e2.intValue()); :‬‬
‫|‬
‫‪expr:e1 DIVIDE expr:e2‬‬
‫}‪{: RESULT = new Integer(e1.intValue() / e2.intValue()); :‬‬
‫|‬
‫‪expr:e1 MOD expr:e2‬‬
‫}‪{: RESULT = new Integer(e1.intValue() % e2.intValue()); :‬‬
‫|‬
‫‪NUMBER:n‬‬
‫}‪{: RESULT = n; :‬‬
‫|‬
‫‪MINUS expr:e‬‬
‫}‪{: RESULT = new Integer(0 - e.intValue()); :‬‬
‫‪%prec UMINUS‬‬
‫|‬
‫‪LPAREN expr:e RPAREN‬‬
‫}‪{: RESULT = e; :‬‬
‫;‬

‫ﺑﺮﻧﺎﻣﻪي ‪ .2‬ﻫﻤﺎن ﻣﺜﺎل ﻗﺒﻠﻲ ﻣﻨﺘﻬﺎ ﺑﺎ ﻗﻮاﻋﺪ ﻣﻌﻨﺎﻳﻲ‪.‬‬

‫ﻫﻤﺎﻧﻄﻮر ﻛﻪ در ﺑﺎﻻ دﻳﺪه ﻣﻲﺷﻮد اﻣﻜﺎن ﻧﺎﻣﮕﺬاري ﻳﻚ ‪ not-terminal‬ﺑﻪ ﻣﻨﻈـﻮر ﻧـﺎم ﺑـﺮدن از آن در ‪ action‬ﻫـﺎ وﺟـﻮد‬
‫دارد‪ .‬ﻣﺜﻼ در‬
‫‪expr:e1 PLUS expr:e2‬‬
‫}‪{: RESULT = new Integer(e1.intValue() + e2.intValue()); :‬‬

‫اوﻟﻴﻦ ‪ e1 ،expr‬و دوﻣﻴﻦ ‪ e2 ،expr‬ﻧﺎﻣﻴﺪه ﺷﺪه اﺳﺖ‪ .‬در ﺿﻤﻦ ﻫﻤﺎﻧﻄﻮر ﻛﻪ ﻣﺸﺎﻫﺪه ﻣﻲﺷﻮد ﺳﻤﺖ ﭼﭗ ﻫـﺮ ﻗﺎﻋـﺪه ﺑـﺎ‬
‫‪ RESULT‬ﻧﺸﺎن داده ﻣﻲﺷﻮد‪.‬‬

‫ﻫﺮ ﻧﻤﺎدي ﻛﻪ در ﻳﻚ ﻗﺎﻋﺪه ﻇﺎﻫﺮ ﻣﻲﺷﻮد در زﻣﺎن اﺟﺮا ﺗﻮﺳﻂ ‪object‬اي از ﻧـﻮع ‪ Symbol‬در ﭘﺸـﺘﻪي ﺗﺠﺰﻳـﻪ‬
‫ﻧﺸﺎن داده ﻣﻲﺷﻮد‪ .‬ﺑﺮﭼﺴﺐﻫﺎ ﺑﻪ ﻣﺘﻐﻴﺮ ‪ value‬در داﺧﻞ آن ‪object‬ﻫﺎ اﺷﺎره ﻣﻲﻛﻨﻨﺪ‪ .‬در ﻣﺜﺎل ﺑﺎﻻ ‪ e1‬و ‪ e2‬ﺑـﻪ اﺷـﻴﺎﻳﻲ از‬
‫ﺟﻨﺲ ‪ Integer‬اﺷﺎره ﻣﻲﻛﻨﻨﺪ‪ .‬اﻳﻦ اﺷﻴﺎ درون ﻓﻴﻠﺪ ‪value‬ي اﺷﻴﺎﻳﻲ از ﺟﻨﺲ ‪ Symbol‬ﻛـﻪ آن ‪non-terminal‬ﻫـﺎ را در‬
‫ﭘﺸﺘﻪ ﺗﺠﺰﻳﻪ ﻧﺸﺎن ﻣﻲدﻫﻨﺪ ﻗﺮار دارﻧﺪ‪ .‬ﻫﻤﭽﻨـﻴﻦ ‪ RESULT‬ﻫـﻢ از ﻧـﻮع ‪ Integer‬اﺳـﺖ ﭼـﻮن در اﻳﻨﺠـﺎ ‪ expr‬از ﻧـﻮع‬
‫‪ Integer‬ﺗﻌﺮﻳﻒ ﺷﺪه اﺳﺖ‪ .‬اﻳﻦ ‪ Integer‬در ﻓﻴﻠﺪ ‪value‬ي ‪ object‬ﺟﺪﻳﺪي از ﻧﻮع ‪ Symbol‬ﻗﺮار ﻣﻲﮔﻴﺮد‪.‬‬

‫ﺑﺮاي ﻫﺮ ﺑﺮﭼﺴﺐ دو ﻣﺘﻐﻴﺮ دﻳﮕﺮ ﻧﻴﺰ ﺑﺮاي ﻛﺎرﺑﺮ ﻗﺎﺑﻞ دﺳﺘﺮﺳﻲ ﻫﺴﺘﻨﺪ‪ .‬دو ﺑﺮﭼﺴﺐ ﭼﭗ و راﺳﺖ وﺟـﻮد دارﻧـﺪ‬
‫ﻛﻪ ﺑﻪ ﻛﺎرﺑﺮ اﻳﻦ اﻣﻜﺎن را ﻣﻲدﻫﻨﺪ ﻛﻪ ﻛﺎرﺑﺮ ﺑﺘﻮاﻧﺪ ﺑﻔﻬﻤﺪ ﺳﻤﺖ ﭼﭗ و راﺳﺖ آن ﻧﻤﺎد در رﺷﺘﻪي ورودي ﻛﺠﺎ ﻗـﺮار دارد‪.‬‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫ﻧﺎم اﻳﻦ ﻣﺘﻐﻴﺮﻫﺎ از اﺿﺎﻓﻪ ﻛﺮدن ﻛﻠﻤﺎت ‪ right‬و ‪ left‬ﺑﻪ اﺳﻢ ﺑﺮﭼﺴﺐ ﻣﻮردﻧﻈﺮ ﺑﻪ دﺳﺖ ﻣﻲآﻳﺪ‪ .‬ﻣﺜﻼ در ﻣﺜﺎل ﻓﻮق ﻋـﻼوه‬
‫ﺑﺮ ‪ e1‬دو ﻣﺘﻐﻴﺮ‪ e1left‬و ‪ e1right‬ﻧﻴﺰ ﺗﻌﺮﻳﻒ ﻣﻲﺷﻮﻧﺪ ﻛﻪ ﻣﻘﺪار آﻧﻬﺎ از ﻧﻮع ‪ int‬اﺳﺖ‪.‬‬

‫ﮔﺎم آﺧﺮ در ﺳﺎﺧﺖ ﻳﻚ ﺗﺠﺰﻳﻪﮔﺮ ﺳﺎﺧﺖ ﻳﻚ ‪) scanner‬ﻳﺎ ﻫﻤﺎن ‪ (lexer‬اﺳـﺖ‪ .‬اﻳـﻦ روال وﻇﻴﻔـﻪي ﺧﻮاﻧـﺪن‬
‫ﺣﺮﻓﻬﺎي ﺟﺪاﮔﺎﻧﻪ و ﺗﺒﺪﻳﻞ آن ﺑﻪ اﺷﻴﺎﻳﻲ از ﺟﻨﺲ ‪ Symbol‬ﻛﻪ اﻃﻼﻋﺎت آن ﻧﻤﺎدﻫﺎ را ﺑﻪ ﺗﺠﺰﻳﻪﮔﺮ ﻣﻲدﻫﺪ ﺑﺮ ﻋﻬـﺪه دارد‪.‬‬
‫ﭘﺎﻳﺎﻧﻪﻫﺎ از ﻃﺮﻳﻖ ﻓﺮاﺧﻮاﻧﻲ روال ‪ scanner‬ﺑﻪ دﺳﺖ ﻣﻲآﻳﻨﺪ‪ .‬در اﻳﻦ ﻣﺜـﺎل ﺗﺠﺰﻳـﻪﮔـﺮ )(‪ scanner.next_token‬را ﺻـﺪا‬
‫ﻣﻲزﻧﺪ‪ scanner .‬ﺑﺎﻳﺪ ﺷﻲءاي از ﻧﻮع ‪ java_cup.runtime.symbol‬ﺑﺮﮔﺮداﻧﺪ‪ .‬اﻳﻦ ﺷﻲء ﺣﺎوي ﻣﺘﻐﻴﺮي ﺑـﻪ ﻧـﺎم ‪value‬‬
‫اﺳﺖ ﻛﻪ ﻣﻘﺪار آن ﻧﻤﺎد را در ﺧﻮد ذﺧﻴﺮه ﻣﻲﻛﻨﺪ و ﻧﻮع آن از ﺟﻨﺲ ﺗﻌﺮﻳﻒ ﺷﺪه در ﺑﺨﺶ ﺗﻌﺮﻳﻒ اﻧـﻮاع اﺳـﺖ‪ .‬در اﻳـﻦ‬
‫ﻣﺜﺎل اﮔﺮ ‪ lexer‬ﺑﺨﻮاﻫﺪ ﻳﻚ ‪ NUMBER‬ﺑﻪ ﺗﺠﺰﻳﻪﮔﺮ ﺑﻔﺮﺳﺘﺪ ﺑﺎﻳﺪ ﻳﻚ ﺷﻲء از ﻧﻮع ‪ Symbol‬ﺑﺴﺎزد و ﻣﻘﺪار ‪ value‬آن‬
‫را ﺑﺎ ﺷﻲءاي از ﻧﻮع ‪ Integer‬ﭘﺮ ﻛﻨﺪ‪ .‬در ﺻﻮرﺗﻲ ﻛﻪ ﻧﻤﺎد ﻣﻮرد ﻧﻈﺮ ‪ terminal‬ﻳﺎ ‪non-terminal‬اي ﺑـﺪون ﻣﻘـﺪار ﺑﺎﺷـﺪ‬
‫‪ null ،value‬ﺧﻮاﻫﺪ ﺑﻮد‪.‬‬

‫ﺑﺮﻧﺎﻣﻪاي ﻛﻪ داﺧﻞ ﺑﺨﺶ ‪ init with‬ﻧﻮﺷﺘﻪ ﻣﻲﺷﻮد ﻗﺒﻞ از اﻳﻨﻜﻪ ﻫﻴﭻ ‪token‬اي ﺗﻘﺎﺿﺎ ﺷﻮد ﺷﻮد اﺟﺮا ﻣﻲﺷﻮد و‬
‫درﺧﻮاﺳﺖ ﻫﺮ ‪ token‬ﺗﻮﺳﻂ ﻛﺪي ﻛﻪ در ﺑﺨﺶ ‪ scan with‬ﻧﻮﺷﺘﻪ ﺷﺪه اﻧﺠﺎم ﻣﻲﺷـﻮد‪ .‬اﻳﻨﻜـﻪ ‪ scanner‬دﻗﻴﻘـﺎ ﭼﮕﻮﻧـﻪ‬
‫ﻋﻤﻞ ﻣﻲﻛﻨﺪ ﺑﺮاي ﻛﺎپ ﻣﻬﻢ ﻧﻴﺴﺖ‪ ،‬ﺗﻨﻬﺎ ﺑﺎﻳﺪ ﺷﻲءاي از ﻧﻮع ‪ Symbol‬ﺑﺎ ﻛﻪ ﺑﻪ ﺻﻮرت درﺳﺖ ﻣﻘﺪاردﻫﻲ ﺷﺪه ﺑﺮﮔﺮداﻧﺪ‪.‬‬
‫در ﺿﻤﻦ اﺳﻜﻨﺮ ﻧﺒﺎﻳﺪ از اﺷﻴﺎي ﺗﻜﺮاري ﺑﺮﮔﺮداﻧﺪ‪.‬‬

‫‪ .3‬ﺳﺎﺧﺘﺎر ﺑﺮﻧﺎﻣﻪ‬

‫ﺣﺎل ﺑﻪ ﺑﺮرﺳﻲ ﺳﺎﺧﺘﺎرﺑﻨﺪي ﺑﺮﻧﺎﻣﻪ در ﻛﺎپ ﻣﻲ ﭘﺮدازﻳﻢ‪ .‬ﻳﻚ ﺑﺮﻧﺎﻣﻪ در ﻛﺎپ ﺷﺎﻣﻞ ‪ 5‬ﺑﺨﺶ ﻣﻲﺑﺎﺷﺪ‪:‬‬
‫‪ ‬ﻣﺸﺨﺺ ﻛﺮدن ‪package‬ﻫﺎ و ‪import‬ﻫﺎ‪.‬‬
‫‪ ‬ﻛﺪﻫﺎﻳﻲ ﻛﺎرﺑﺮ‬
‫‪ ‬ﺗﻌﺮﻳﻒ ﻧﻤﺎدﻫﺎي ﮔﺮاﻣﺮ‬
‫‪ ‬ﺑﻴﺎن اوﻟﻮﻳﺖﻫﺎ‬
‫‪ ‬ﺷﺮح ﮔﺮاﻣﺮ‬

‫ﻣﺸﺨﺺ ﻛﺮدن ‪package‬ﻫﺎ و ‪import‬ﻫﺎ‬


‫ﻣﺸﺨﺺ ﻛﺮدن ﮔﺮاﻣﺮ ﺑﺎ ﺗﻌﺮﻳﻒ ‪package‬ﻫﺎ و ‪import‬ﻫﺎ اﻧﺠﺎم ﻣﻲﺷﻮد )اﻳﻦ ﺑﺨﺶ اﺧﺘﻴﺎري ﻣﻲﺑﺎﺷﺪ(‪ .‬ﺷﻜﻞ ﺗﻌﺮﻳﻒ آن‪-‬‬
‫ﻫﺎ ﻫﻤﺎﻧﻨﺪ ﻳﻚ ﺑﺮﻧﺎﻣﻪي ﺟﺎواي ﻋﺎدي ﻣﻲﺑﺎﺷﺪ‪ .‬ﺗﻌﺮﻳﻒ ﻳﻚ ‪ package‬ﺑﻪ ﺷﻜﻞ زﻳﺮ ﻣﻲﺑﺎﺷﺪ‪:‬‬
‫;‪package name‬‬
‫ﻛﻪ در آن ‪ name‬ﻣﺸﺨﺺﻛﻨﻨﺪهي ‪ package‬ﺟﺎوا ﻣﻲﺑﺎﺷﺪ ﻛﻪ اﺣﺘﻤﺎﻻ ﺗﻮﺳﻂ "‪ ".‬ﺑﻪ ﭼﻨﺪ ﺑﺨـﺶ ﺗﺒـﺪﻳﻞ ﺷـﺪه اﺳـﺖ‪ .‬در‬
‫واﻗﻊ در ﺣﺎﻟﺖ ﻛﻠﻲ ﻛﺎپ از ﻗﺮاردادﻫﺎي ﺗﺤﻠﻴﻠﮕﺮ ‪ lexical‬ﺧﻮد ﺟﺎوا اﺳﺘﻔﺎده ﻣﻲﻛﻨﺪ‪ .‬ﺑﺮاي ﻣﺜﺎل ﻫـﺮ دو ﻣـﺪل ‪comment‬‬
‫در ﺟﺎوا را ﭘﺸﺘﻴﺒﺎﻧﻲ ﻣﻲﻛﻨﺪ و ‪id‬ﻫﺎ ﺑﺎ ﻳﻚ ﺣﺮف‪ "$" ،‬ﻳﺎ "_" ﺷﺮوع ﺷﺪه و ﺑﺎ ﺗﻌﺪادي ﺣﺮف‪ ،‬رﻗـﻢ‪ "$" ،‬ﻳـﺎ "_" اداﻣـﻪ‬
‫ﻣﻲﻳﺎﺑﻨﺪ‪ .‬ﺗﻌﺮﻳﻒ ‪ package‬ﺗﻌﻴﻴﻦ ﻣﻲﻛﻨﺪ ﻛﻪ ﻛﻼﺳﻬﺎي ‪ sym‬و ‪parser‬اي ﻛﻪ اﻳﺠﺎد ﻣﻲﺷﻮﻧﺪ درون ﭼﻪ ‪package‬اي ﻗﺮار‬
‫دارﻧﺪ‪.‬‬

‫ﺗﻌﺮﻳﻒ ‪import‬ﻫﺎ ﻫﻢ ﻣﺜﻞ ﺟﺎوا و ﺑﻪ ﺷﻜﻞ زﻳﺮ ﻣﻲﺑﺎﺷﺪ‪:‬‬


‫;‪import package_name.class_name‬‬
‫ﻳﺎ‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫;*‪import package_name.‬‬

‫‪import‬ﻫﺎ ﺑﻪ ﻫﻤﺎن ﺷﻜﻠﻲ ﻛﻪ ﻧﻮﺷﺘﻪ ﻣﻲﺷﻮﻧﺪ ﺑﻪ ﻛﺪ ﺗﺠﺰﻳﻪﮔﺮ ﻣﻨﺘﻘﻞ ﻣﻲﺷﻮﻧﺪ و اﺟﺎزه ﻣﻲدﻫﻨﺪ ﻛﻪ در ‪action‬ﻫﺎ از آن‬
‫ﻛﻼﺳﻬﺎ اﺳﺘﻔﺎده ﺷﻮد‪.‬‬

‫ﻛﺪﻫﺎي ﻛﺎرﺑﺮ‬
‫ﭘﺲ از دو ﺑﺨﺶ اﺧﺘﻴﺎري ﻓﻮق ﻳﻚ ﺑﺨﺶ اﺧﺘﻴﺎري دﻳﮕﺮ ﻗﺮار دارد ﻛﻪ ﺑﻪ ﻛﺎرﺑﺮ اﺟﺎزه ﻣﻲدﻫﺪ ﻛﺪﻫﺎي ﻣـﻮرد ﻧﻴـﺎزش را در‬
‫ﺗﺠﺰﻳﻪﮔﺮ ﻧﻬﺎﻳﻲ ﺟﺎﺳﺎزي ﻛﻨﺪ‪ .‬ﺑﻪ ﻋﻨﻮان ﺑﺨﺸﻲ از ﻛﺪ ﺗﺠﺰﻳﻪﮔﺮ ﻳﻚ ﻛﻼس ﻏﻴﺮ ﻋﻤﻮﻣﻲ‪ 1‬ﻛﻪ ﺗﻤﺎم ‪action‬ﻫﺎي ﺗﻌﺒﻴﻪﺷﺪه را‬
‫در ﺑﺮ دارد ﺗﻮﻟﻴﺪ ﻣﻲﺷﻮد‪ .‬در اﺑﺘﺪاي ﺑﺮﻧﺎﻣﻪ ﻳﻚ ﺑﺨﺶ ﺑﺎ ﻧﺎم ‪ action code‬ﺑﻪ ﻛﺎرﺑﺮ اﺟﺎزه ﻣﻲدﻫﺪ ﻛﻪ ﻛﺪﻫﺎﻳﻲ را ﻣﺴـﺘﻘﻴﻤﺎ‬
‫در اﻳﻦ ﻛﻼس وارد ﻛﻨﺪ‪ .‬ﻛﺪﻫﺎ و ﻣﺘﻐﻴﺮﻫﺎﻳﻲ ﻛﻪ ﺗﻮﺳﻂ ﺑﻴﺸﺘﺮ ‪action‬ﻫﺎ اﺳﺘﻔﺎده ﻣﻲﺷﻮﻧﺪ ﻣﻌﻤﻮﻻ در اﻳﻦ ﺑﺨـﺶ ﻗـﺮار ﻣـﻲ‪-‬‬
‫ﮔﻴﺮﻧﺪ )ﺑﻪ ﻋﻨﻮان ﻣﺜﺎل رواﻟﻬﺎي ﻛﺎر ﺑﺎ ‪ . (symbol table‬اﻳﻦ ﺑﺨﺶ ﺑﻪ ﺷﻜﻞ زﻳﺮ ﺗﻌﺮﻳﻒ ﻣﻲﺷﻮد‪:‬‬
‫;}‪action code {: ... :‬‬
‫ﻛﻪ در آن داﺧﻞ }‪ {: ... :‬ﻛﺪي ﻗﺮار دارد ﻛﻪ ﻣﺴﺘﻘﻴﻤﺎ داﺧﻞ ﺗﻌﺮﻳﻒ ﻛﻼس ‪ action class‬ﻗﺮار ﻣﻲﮔﻴﺮد‪.‬‬

‫ﺑﻌﺪ از اﻳﻦ ﺑﺨﺶ ﻳﻚ ﺑﺨﺶ اﺧﺘﻴﺎري دﻳﮕﺮ ﺑﻪ ﻧﺎم ‪ parser code‬ﻗﺮار ﻣﻲﮔﻴﺮد‪ .‬اﻳﻦ ﺑﺨﺶ ﺑﻪ ﻛﺎرﺑﺮ اﺟﺎزه ﻣﻲدﻫﺪ‬
‫ﻛﺪي را ﺑﻪ ﻃﻮر ﻣﺴﺘﻘﻴﻢ در ﻛﻼس ﺧﻮد ﺗﺠﺰﻳﻪﮔﺮ ﻗﺮار دﻫﺪ‪ .‬از ﺟﻤﻠﻪ ﻛﺎرﺑﺮدﻫﺎي آن ﻣﻲﺗﻮان ﺑﻪ ﻗﺮار دادن روالﻫﺎي‬
‫‪ scanning‬در داﺧﻞ ‪ parser‬و ﻳﺎ ‪override‬ﻛﺮدن رواﻟﻬﺎي ﭘﻴﺶﻓﺮض ﮔﺰارش ﺧﻄﺎ اﺷﺎره ﻛﺮد‪ .‬ﺗﻌﺮﻳﻒ اﻳﻦ ﺑﺨﺶ ﻧﻴﺰ‬
‫درﺳﺖ ﻣﺜﻞ ‪ action code‬و ﺑﻪ ﺻﻮرت زﻳﺮ اﺳﺖ‪:‬‬

‫;}‪parser code {: ... :‬‬

‫ﺑﻌﺪ از ‪ parser code‬ﻳﻚ ﺑﺨﺶ اﺧﺘﻴﺎري دﻳﮕﺮ ﺗﺤﺖ ﻋﻨﻮان ‪ init with‬ﻗﺮار ﻣﻲﮔﻴﺮد ﻛﻪ ﺑﻪ ﺷﻜﻞ زﻳﺮ ﺗﻌﺮﻳﻒ‬
‫ﻣﻲﺷﻮد‪:‬‬

‫;}‪init with {: ... :‬‬

‫اﻳﻦ ﺑﺨﺶ ﻛﺪي را ﻣﺸﺨﺺ ﻣﻲﻛﻨﺪ ﻛﻪ ﻗﺒﻞ از اﻳﻨﻜﻪ ﺗﺠﺰﻳﻪﮔﺮ درﺧﻮاﺳﺖ اوﻟﻴﻦ ﺗﻮﻛﻦ را ﺑﺪﻫﺪ اﺟﺮا ﻣﻲﺷﻮد‪ .‬اﻳﻦ ﺑﺨﺶ‬
‫ﻣﻌﻤﻮﻻ ﺑﺮاي ﻣﻘﺪاردﻫﻲ اوﻟﻴﻪ ﺑﻪ ‪ scanner‬و ﺟﺪاول و دﻳﮕﺮ ﺳﺎﺧﺘﺎرﻫﺎي دادهي ﻣﻮرد ﻧﻴﺎز اﺳﺘﻔﺎده ﻣﻲﺷﻮد‪ .‬اﻳﻦ ﺑﺨﺶ‬
‫ﺑﺪﻧﻪي ﻳﻚ روال ‪ void‬را داﺧﻞ ﻛﻼس ‪ parser‬ﺗﺸﻜﻴﻞ ﻣﻲدﻫﺪ‪.‬‬

‫آﺧﺮﻳﻦ ﺑﺨﺸﻲ ﻛﻪ ﻣﻲﺗﻮاﻧﺪ ﺗﻌﻴﻴﻦ ﺷﻮد)اﺧﺘﻴﺎري( ﺗﻌﻴﻴﻦ اﻳﻦ اﺳﺖ ﻛﻪ ﺗﺠﺰﻳﻪﮔﺮ ﭼﮕﻮﻧﻪ ﺑﺎﻳﺪ درﺧﻮاﺳﺖ ﮔﺮﻓﺘﻦ‬
‫ﺗﻮﻛﻦ ﺑﻌﺪي را ﺑﺪﻫﺪ‪ .‬ﺗﻌﺮﻳﻒ آن ﺑﻪ ﺷﻜﻞ زﻳﺮ اﺳﺖ‪:‬‬

‫;}‪scan with {: ... :‬‬

‫ﻫﻤﺎﻧﻨﺪ ﺣﺎﻟﺖ ‪ init‬اﻳﻦ ﺑﺨﺶ ﺑﺪﻧﻪي ﻳﻚ روال از ﻛﻼس ﺗﺠﺰﻳﻪﮔﺮ را ﺗﺸﻜﻴﻞ ﻣﻲدﻫﺪ‪ .‬وﻟﻲ ﺑﺮ ﺧﻼف آن اﻳﻦ روال ﺷﻲءاي‬
‫از ﻧﻮع ‪ java_cup.runtime.Symbol‬ﺑﺮ ﻣﻲﮔﺮداﻧﺪ‪ .‬ﺑﻨﺎﻳﺒﺮاﻳﻦ ﻛﺪ ﻧﻮﺷﺘﻪ ﺷﺪه در اﻳﻦ ﺑﺨﺶ ﺑﺎﻳﺪ ﻣﻘﺪاري از آن ﻧﻮع‬
‫ﺑﺮﮔﺮداﻧﺪ‪.‬‬

‫‪1‬‬
‫‪Non-public‬‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫ﺗﻌﺮﻳﻒ ﻧﻤﺎدﻫﺎي ﮔﺮاﻣﺮ‬

‫اﻳﻦ ﺑﺨﺶ ﻛﻪ ﻧﻮﺷﺘﻦ آن ﺿﺮوري اﺳﺖ ﺑﺮاي ﺗﻌﺮﻳﻒ ﻛﺮدن ﭘﺎﻳﺎﻧﻪﻫﺎي و ﻧﺎﭘﺎﻳﺎﻧﻪﻫﺎي زﺑﺎن و ﺑﻴﺎن ﻧﻮع آﻧﻬﺎ ﺑﻪ ﻛﺎر ﻣﻲرود‪.‬‬
‫ﻫﻤﺎﻧﻄﻮر ﻛﻪ در ﺑﺎﻻ ﮔﻔﺘﻪ ﺷﺪ ﻫﺮ ﭘﺎﻳﺎﻧﻪ ﻳﺎ ﻧﺎﭘﺎﻳﺎﻧﻪ در زﻣﺎن اﺟﺮا ﺗﻮﺳﻂ ﻳﻚ ﺷﻲء از ﻛﻼس ‪ Symbol‬ﻧﺸﺎن داده ﻣﻲﺷﻮد‪ .‬در‬
‫ﻣﻮرد ﭘﺎﻳﺎﻧﻪﻫﺎ آﻧﻬﺎ ﺗﻮﺳﻂ ﺗﺠﺰﻳﻪﮔﺮ ﺑﺮﮔﺮداﻧﺪه ﻣﻲﺷﻮﻧﺪ و در ﭘﺸﺘﻪي ﺗﺠﺰﻳﻪ ﻗﺮار ﻣﻲﮔﻴﺮﻧﺪ‪ lexer .‬ﺑﺎﻳﺪ ﻣﻘﺪار آن ﭘﺎﻳﺎﻧﻪ در‬
‫در ﻓﻴﻠﺪ ‪ value‬آن ﻗﺮار دﻫﺪ‪ .‬در ﻣﻮرد ﻧﺎﭘﺎﻳﺎﻧﻪﻫﺎ ﻧﻴﺰ زﻣﺎﻧﻲ ﻳﻚ ﻧﺎﭘﺎﻳﺎﻧﻪ در ﭘﺸﺘﻪ ﻇﺎﻫﺮ ﻣﻲﺷﻮد ﻛﻪ ﻳﻚ ﺳﺮي ﻧﻤﺎد ﻛﻪ ﻣﻌﺎدل‬
‫ﺳﻤﺖ راﺳﺖ ﻳﻚ ﻗﺎﻋﺪه ﻫﺴﺘﻨﺪ ‪ reduce‬ﺷﻮﻧﺪ و ﻧﺎﭘﺎﻳﺎﻧﻪي ﻣﻌﺎدل ﺟﺎي آﻧﻬﺎ ﻗﺮار ﮔﻴﺮد‪ .‬ﺑﺮاي ﺗﻌﻴﻴﻦ اﻳﻨﻜﻪ ﻳﻚ ﻧﻤﺎد ﭘﺎﻳﺎﻧﻪ ﻳﺎ‬
‫ﻧﺎﭘﺎﻳﺎﻧﻪ اﺳﺖ ﺑﺎﻳﺪ از دو ﻧﻤﺎد ‪ terminal‬و ‪ non terminal‬اﺳﺘﻔﺎده ﻛﺮد‪ .‬ﺑﻪ ﺷﻜﻞﻫﺎي زﻳﺮ ﻣﻲﺗﻮان ﻧﻤﺎدﻫﺎي ﮔﺮاﻣﺮ را‬
‫ﺗﻌﺮﻳﻒ ﻛﺮد‪:‬‬

‫;‪terminal classname name1, name2, ...‬‬


‫;‪non terminal classname name1, name2, ...‬‬
‫;‪terminal name1, name2, ...‬‬
‫;‪non terminal name1, name2, ...‬‬

‫ﻛﻪ در آن ‪ classname‬ﻧﻮع ﻣﻘﺪار آن ﭘﺎﻳﺎﻧﻪ ﻳﺎ ﻧﺎﭘﺎﻳﺎﻧﻪ را ﻣﺸﺨﺺ ﻣﻲﻛﻨﺪ‪ .‬اﻳﻦ ﻧﻮع زﻣﺎﻧﻲ ﻛﻪ ﻛﺎرﺑﺮ از ﻃﺮﻳﻖ ﻳﻚ ﺑﺮﭼﺴﺐ‬
‫ﺑﻪ آن ﻧﻤﺎد دﺳﺘﺮﺳﻲ ﭘﻴﺪا ﻣﻲﻛﻨﺪ اﺳﺘﻔﺎده ﻣﻲﺷﻮد‪ .‬اﮔﺮ ﻧﻮﻋﻲ ﻣﺸﺨﺺ ﻧﺸﻮد آن ﻧﻤﺎد ﻣﻘﺪاري ﻧﺨﻮاﻫﺪ داﺷﺖ‪ .‬اﺳﻢ ﻧﻤﺎدﻫﺎ‬
‫ﻧﺒﺎﻳﺪ ﺟﺰو ﻛﻠﻤﺎت رزروﺷﺪهي ﻛﺎپ ﻳﻌﻨﻲ‪،"nonterminal" ،"non" ،"terminal" ،"parser" ،"action" ،"code" :‬‬
‫"‪ "import" ،"nonassoc" ،"right" ،"left" ،"precedence" ،"start" ،"with" ،"scan" ،"init‬و "‪"package‬‬
‫ﺑﺎﺷﺪ‪.‬‬

‫ﺑﻴﺎن اوﻟﻮﻳﺖﻫﺎ‬

‫اﻳﻦ ﺑﺨﺶ ﻛﻪ ﻧﻮﺷﺘﻦ آن اﺧﺘﻴﺎري ﻣﻲﺑﺎﺷﺪ‪ ،‬اوﻟﻮﻳﺖ ﻋﻤﻠﮕﺮﻫﺎ و ﻧﺤﻮهي ﺷﺮﻛﺖﭘﺬﻳﺮي آﻧﻬﺎ را ﻣﺸﺨﺺ ﻣﻲﻛﻨﺪ‪ .‬اﻳﻦ ﺑﺨﺶ‬
‫زﻣﺎﻧﻲ ﻣﻔﻴﺪ اﺳﺖ ﻛﻪ ﻣﻲﺧﻮاﻫﻴﻢ از ﮔﺮاﻣﺮﻫﺎي ﻣﺒﻬﻢ اﺳﺘﻔﺎده ﻛﻨﻴﻢ‪ ،‬ﻫﻤﺎﻧﻄﻮر ﻛﻪ در ﺑﺮﻧﺎﻣﻪي ‪ 2‬اﺳﺘﻔﺎده ﻛﺮدﻳﻢ‪ .‬ﺳﻪ ﻧﻮع‬
‫ﺗﻌﺮﻳﻒ اوﻟﻮﻳﺖ و ﺷﺮﻛﺖﭘﺬﻳﺮي وﺟﻮد دارد‪:‬‬

‫;]‪precedence left terminal[, terminal...‬‬


‫;]‪precedence right terminal[, terminal...‬‬
‫;]‪precedence nonassoc terminal[, terminal...‬‬
‫ﻟﻴﺴﺘﻲ ﻛﻪ اﺟﺰاي آن ﺑﺎ وﻳﺮﮔﻮل از ﻫﻢ ﺟﺪا ﺷﺪهاﻧﺪ ﻣﺸﺨﺺ ﻣﻲﻛﻨﺪ ﻛﻪ اﺟﺰاي آن ﻟﻴﺴﺖ ﻫﻤﮕﻲ اوﻟﻮﻳﺖ ﻳﻜﺴﺎﻧﻲ دارﻧﺪ و‬
‫داراي ﺷﺮﻛﺖﭘﺬﻳﺮي ﺑﻪ ﺷﻜﻞ ﻣﺸﺨﺺ ﺷﺪه ﻣﻲﺑﺎﺷﻨﺪ‪ .‬ﺑﻪ ﺗﺮﺗﻴﺐ اوﻟﻴﻦ ﺧﻂ ﻛﻤﺘﺮﻳﻦ اوﻟﻮﻳﺖ و آﺧﺮﻳﻦ ﺧﻂ ﺑﻴﺸﺘﺮﻳﻦ‬
‫اوﻟﻮﻳﺖ را دارﻧﺪ‪ .‬ﺑﻪ ﻋﻨﻮان ﻣﺜﺎل ﭼﻮن ﺿﺮب و ﺗﻘﺴﻴﻢ اوﻟﻮﻳﺖ ﺑﺎﻻﺗﺮي از ﺟﻤﻊ و ﺗﻔﺮﻳﻖ دارﻧﺪ ﺧﻮاﻫﻴﻢ داﺷﺖ‪:‬‬
‫;‪precedence left ADD, SUBTRACT‬‬
‫;‪precedence left TIMES, DIVIDE‬‬
‫اوﻟﻮﻳﺖﻫﺎ ﺑﺎﻋﺚ ﺣﻞ ﺷﺪن ﺗﺪاﺧﻞﻫﺎي ﺷﻴﻔﺖ‪-‬ﻛﺎﻫﺶ ﻣﻲﺷﻮﻧﺪ‪ .‬ﺑﻪ ﻋﻨﻮان ﻣﺜﺎل در ﻋﺒﺎرت ‪ 3 + 4 * 8‬ﺗﺠﺰﻳﻪﮔﺮ ﻧﻤﻲداﻧﺪ‬
‫ﭘﺲ از دﻳﺪن ‪ 3 + 4‬ﺑﺎﻳﺪ ﻛﺎﻫﺶ اﻧﺠﺎم دﻫﺪ ﻳﺎ اﻳﻨﻜﻪ * را وارد ﭘﺸﺘﻪ ﻛﻨﺪ‪ .‬اﻣ‪‬ﺎ ﭼﻮن * اوﻟﻮﻳﺖ ﺑﺎﻻﺗﺮي دارد ﺷﻴﻔﺖ اﻧﺠﺎم‬
‫ﺧﻮاﻫﺪ ﺷﺪ و ﺿﺮب ﻗﺒﻞ از ﺟﻤﻊ اﻧﺠﺎم ﺧﻮاﻫﺪ ﺷﺪ‪.‬‬

‫ﻛﺎپ ﺑﺮ اﺳﺎس ﺗﻌﺮﻳﻒ اوﻟﻮﻳﺘﻬﺎ ﺑﻪ ﻫﺮ ﭘﺎﻳﺎﻧﻪ ﻳﻚ اوﻟﻮﻳﺖ ﻧﺴﺒﺖ ﻣﻲدﻫﺪ‪ .‬اﮔﺮ ﻳﻚ ﭘﺎﻳﺎﻧﻪ در اﻳﻦ ﻟﻴﺴﺖ ﻧﻴﺎﻳﺪ‬
‫ﻛﻤﺘﺮﻳﻦ اوﻟﻮﻳﺖ را ﺧﻮاﻫﺪ داﺷﺖ‪ .‬ﻛﺎپ ﻫﻤﭽﻨﻴﻦ ﺑﻪ ﻫﺮ ﻗﺎﻋﺪه ﻳﻚ اوﻟﻮﻳﺖ ﻧﺴﺒﺖ ﻣﻲدﻫﺪ‪ .‬آن اوﻟﻮﻳﺖ ﺑﺮاﺑﺮ اﺳﺖ ﺑﺎ‬
‫اوﻟﻮﻳﺖ آﺧﺮﻳﻦ ﭘﺎﻳﺎﻧﻪي آن ﻗﺎﻋﺪه‪ .‬اﮔﺮ آن ﻗﺎﻋﺪه ﺷﺎﻣﻞ ﻫﻴﭻ ﭘﺎﻳﺎﻧﻪاي ﻧﺒﻮد ﻛﻤﺘﺮﻳﻦ اوﻟﻮﻳﺖ را ﺧﻮاﻫﺪ داﺷﺖ‪ .‬ﺑﻪ ﻋﻨﻮان ﻣﺜﺎل‪:‬‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫‪ expr ::= expr TIMES expr‬اوﻟﻮﻳﺘﻲ ﺑﺮاﺑﺮ ‪ TIMES‬ﺧﻮاﻫﺪ داﺷﺖ‪ .‬زﻣﺎﻧﻲ ﻛﻪ ﺗﺪاﺧﻞ ﺷﻴﻔﺖ‪-‬ﻛﺎﻫﺶ وﺟﻮد دارد ﻛﺎپ‬
‫ﺗﺸﺨﻴﺺ ﻣﻲدﻫﺪ ﻛﻪ ﭘﺎﻳﺎﻧﻪاي ﻛﻪ ﻣﻲﺧﻮاﻫﺪ ﺷﻴﻔﺖ ﭘﻴﺪا ﻛﻨﺪ اوﻟﻮﻳﺖ ﺑﻴﺸﺘﺮي دارد و ﻳﺎ ﻗﺎﻋﺪهاي ﻛﻪ ﻣﻲﺧﻮاﻫﺪ ﻛﺎﻫﺶ ﭘﻴﺪا‬
‫ﻛﻨﺪ‪ .‬ﺑﺮ اﺳﺎس آن ﺗﺼﻤﻴﻢ ﻣﻲﮔﻴﺮد ﻛﻪ ﺷﻴﻔﺖ را اﻧﺠﺎم دﻫﺪ ﻳﺎ ﻛﺎﻫﺶ را‪ .‬در ﺻﻮرﺗﻲ ﻛﻪ اوﻟﻮﻳﺖ آﻧﻬﺎ ﻳﻜﺴﺎن ﺑﻮد ﺷﺮﻛﺖ‪-‬‬
‫ﭘﺬﻳﺮي آن ﭘﺎﻳﺎﻧﻪ ﺗﻌﻴﻴﻦ ﻣﻲﻛﻨﺪ ﻛﻪ ﭼﻪ اﺗﻔﺎﻗﻲ ﺑﻴﺎﻓﺘﺪ‪.‬‬

‫ﺑﻪ ﻫﺮ ﭘﺎﻳﺎﻧﻪاي ﻛﻪ در ﻟﻴﺴﺖ ﺑﺎﻻ ﻇﺎﻫﺮ ﺷﻮد ﻳﻚ ﻧﺤﻮهي ﺷﺮﻛﺖﭘﺬﻳﺮي ﻧﺴﺒﺖ داده ﻣﻲﺷﻮد‪ .‬ﺳﻪ ﻧﻮع ﺷﺮﻛﺖ‪-‬‬
‫ﭘﺬﻳﺮي دارﻳﻢ‪ right ،left :‬و ‪ . nonassoc‬در ﺷﺮاﻳﻄﻲ ﻛﻪ اوﻟﻮﻳﺖﻫﺎي ﺑﺮاﺑﺮي دارﻳﻢ از ﺷﺮﻛﺖﭘﺬﻳﺮي ﺑﺮاي ﺗﻌﻴﻴﻦ ﻛﺎري‬
‫ﻛﻪ ﺑﺎﻳﺪ اﻧﺠﺎم ﺷﻮد اﺳﺘﻔﺎده ﻣﻲﺷﻮد‪ .‬اﮔﺮ آن ﭘﺎﻳﺎﻧﻪ از ﭼﭗ ﺷﺮﻛﺖﭘﺬﻳﺮ ﺑﺎﺷﺪ ﻛﺎﻫﺶ اﻧﺠﺎم ﻣﻲﺷﻮد‪ .‬ﻳﻌﻨﻲ اﮔﺮ رﺷﺘﻪي‬
‫ورودي ﺑﻪ ﺷﻜﻞ ‪ 3 + 4 + 5‬ﺑﺎﺷﺪ ﺗﺠﺰﻳﻪﮔﺮ ﻫﻤﻴﺸﻪ ﻛﺎﻫﺶ را از ﭼﭗ ﺑﻪ راﺳﺖ اﻧﺠﺎم ﻣﻲدﻫﺪ‪ .‬ﻳﻌﻨﻲ در اﻳﻦ ﻣﺜﺎل اول ‪+ 4‬‬
‫‪ 3‬اﻧﺠﺎم ﻣﻲﺷﻮد‪ .‬اﮔﺮ ﭘﺎﻳﺎﻧﻪ از راﺳﺖ ﺷﺮﻛﺖﭘﺬﻳﺮ ﺑﺎﺷﺪ ﺑﻪ درون ﭘﺸﺘﻪ ﺷﻴﻔﺖ ﭘﻴﺪا ﻣﻲﻛﻨﺪ‪ .‬ﺑﻨﺎﺑﺮاﻳﻦ ﻛﺎﻫﺶ از راﺳﺖ ﺑﻪ ﭼﭗ‬
‫اﻧﺠﺎم ﻣﻲﺷﻮد‪ ،‬ﺑﻨﺎﺑﺮاﻳﻦ اﮔﺮ ‪ PLUS‬از راﺳﺖ ﺷﺮﻛﺖﭘﺬﻳﺮ ﺗﻌﺮﻳﻒ ﺷﺪه ﺑﻮد اﺑﺘﺪا ‪ 4 + 5‬اﻧﺠﺎم ﻣﻲﺷﺪ‪ .‬اﮔﺮ ﭘﺎﻳﺎﻧﻪاي از ﻧﻮع‬
‫‪ nonassoc‬ﺗﻌﺮﻳﻒ ﺷﻮد وﻗﻮع ﻣﺘﻮاﻟﻲ دوﺗﺎ از آن ﻛﻪ اوﻟﻮﻳﺖ ﻣﺴﺎوياي دارﻧﺪ ﺑﺎﻋﺚ اﻳﺠﺎد ﺧﻄﺎ ﺧﻮاﻫﺪ ﺷﺪ‪ .‬اﻳﻦ ﻣﻮﺿﻮع‬
‫ﺑﺮاي ﻋﻤﻠﮕﺮ "==" ﻣﻨﺎﺳﺐ اﺳﺖ‪ .‬ﺑﺮاي ﻣﺜﺎل اﮔﺮ "==" از ﻧﻮع ‪ nonassoc‬ﺗﻌﺮﻳﻒ ﺷﺪه ﺑﺎﺷﺪ ﻋﺒﺎرت ‪ 3 == 4 == 5‬ﺑﺎﻋﺚ‬
‫وﻗﻮع ﺧﻄﺎ ﺧﻮاﻫﺪ ﺷﺪ‪ .‬اﮔﺮ ﺑﺎ ﺗﻮﺟﻪ ﺑﻪ ﻗﻮاﻋﺪ ﻓﻮق ﺗﺪاﺧﻠﻲ رﻓﻊ ﻧﺸﺪ ﺑﻪ ﻛﺎرﺑﺮ ﮔﺰارش ﻣﻲﺷﻮد‪.‬‬

‫ﺷﺮح ﮔﺮاﻣﺮ‬

‫آﺧﺮﻳﻦ ﮔﺎم در ﺑﻴﺎن وﻳﮋﮔﻲﻫﺎي ﺗﺠﺰﻳﻪﮔﺮ ﺷﺮح ﮔﺮاﻣﺮ آن اﺳﺖ‪ .‬اﻳﻦ ﺑﺨﺶ ﺑﻪ ﻃﻮر اﺧﺘﻴﺎري ﺑﺎ ﻋﺒﺎرﺗﻲ ﺑﻪ ﺷﻜﻞ زﻳﺮ آﻏﺎز‬
‫ﻣﻲﺷﻮد‪:‬‬

‫;‪start with non-terminal‬‬

‫اﻳﻦ ﻋﺒﺎرت ﺗﻌﻴﻴﻦ ﻣﻲﻛﻨﺪ ﻛﻪ ﻧﺎﭘﺎﻳﺎﻧﻪي آﻏﺎزﻳﻦ ﻛﺪام اﺳﺖ‪ .‬اﮔﺮ آن را ﻣﺸﺨﺺ ﻧﻜﻨﻴﻢ ﺑﻪ ﻃﻮر ﭘﻴﺶﻓﺮض ﻧﺎﭘﺎﻳﺎﻧﻪي ﺳﻤﺖ‬
‫ﭼﭗ او‪‬ﻟﻴﻦ ﻗﺎﻋﺪه ﺑﻪ ﻋﻨﻮان ﺷﺮوع در ﻧﻈﺮ ﮔﺮﻓﺘﻪ ﺧﻮاﻫﺪ ﺷﺪ‪ .‬در ﺿﻤﻦ ﭘﺲ از ﻳﻚ ﺗﺠﺰﻳﻪي ﻣﻮﻓﻖ‪ ،‬ﻛﺎپ ﺷﻲءاي از ﻧﻮع‬
‫‪ Symbol‬ﺑﺮ ﻣﻲﮔﺮداﻧﺪ ﻛﻪ ‪ value‬آن ﻣﻘﺪار ﻧﻬﺎﻳﻲ آﺧﺮﻳﻦ ﻛﺎﻫﺶ را در ﺧﻮد دارد‪.‬‬

‫ﭘﺲ از ﻋﺒﺎرت ﻓﻮق ﺗﻌﺮﻳﻒ ﺧﻮد ﮔﺮاﻣﺮ ﻗﺮار ﻣﻲﮔﻴﺮد‪ .‬ﺗﻤﺎم ﻗﻮاﻋﺪ ﺑﻪ ﺻﻮرت ﻳﻚ ﻧﺎﭘﺎﻳﺎﻧﻪ ﻛﻪ ﺑﻌﺪ از آن "=‪“::‬‬
‫ﻗﺮار ﮔﺮﻓﺘﻪ اﺳﺖ و ﺑﻌﺪ از آن ﺗﻌﺪادي ‪ ،action‬ﭘﺎﻳﺎﻧﻪ و ﻧﺎﭘﺎﻳﺎﻧﻪ ﻗﺮار دارد و ﺑﻌﺪ از آن ﻋﺒﺎرت اﺧﺘﻴﺎرياي ﺑﺮاي ﺑﻴﺎن اوﻟﻮﻳﺖ‬
‫ﻗﺮار ﮔﺮﻓﺘﻪ و ﺳﭙﺲ ﺑﻪ ";" ﻣﻨﺘﻬﻲ ﺷﺪه اﺳﺖ‪ ،‬ﻣﻲﺑﺎﺷﻨﺪ‪ .‬ﻫﺮ ﻧﻤﺎدي در ﺳﻤﺖ راﺳﺖ ﺑﻪ ﻃﻮر اﺧﺘﻴﺎري ﻣﻲﺗﻮاﻧﺪ داراي ﻳﻚ‬
‫ﺑﺮﭼﺴﺐ ﺑﺎﺷﺪ و ﻧﺎم آن ﺑﻌﺪ از ﻧﻤﺎد ﻣﻮرد ﻧﻈﺮ ﻧﻮﺷﺘﻪ ﻣﻲﺷﻮد و ﺑﻴﻦ آن دو ﻳﻚ "‪ ":‬ﻗﺮار ﻣﻲﮔﻴﺮد‪ .‬ﺑﺮﭼﺴﺐﻫﺎ ﺑﺎﻳﺪ داﺧﻞ آن‬
‫ﻗﺎﻋﺪه ﻳﻜﺘﺎ ﺑﺎﺷﻨﺪ و ﺑﺮاي اﺷﺎره ﻛﺮدن ﺑﻪ ﻣﻘﺪار آن ﻧﻤﺎدﻫﺎ در ‪action‬ﻫﺎ اﺳﺘﻔﺎده ﻣﻲﺷﻮﻧﺪ‪ .‬ﻫﻤﺎﻧﻄﻮر ﻛﻪ ﭘﻴﺸﺘﺮ ﻧﻴﺰ ﺗﻮﺿﻴﺢ‬
‫داده ﺷﺪ دو ﻣﺘﻐﻴﺮ دﻳﮕﺮ ﻧﻴﺰ ﺳﺎﺧﺘﻪ ﻣﻲﺷﻮﻧﺪ ﻛﻪ ﻧﺎم اﻳﻦ ﻣﺘﻐﻴﺮﻫﺎ از اﺿﺎﻓﻪ ﻛﺮدن ﻛﻠﻤﺎت ‪ right‬و ‪ left‬ﺑﻪ اﺳﻢ ﺑﺮﭼﺴﺐ‬
‫ﻣﻮردﻧﻈﺮ ﺑﻪ دﺳﺖ ﻣﻲآﻳﺪ‪ .‬اﻳﻦ ﻣﺘﻐﻴﺮﻫﺎ ﺣﺎوي ﻣﻘﺪارﻫﺎي ‪int‬اي ﻫﺴﺘﻨﺪ ﻛﻪ ﻣﺤﻞ راﺳﺖﺗﺮﻳﻦ و ﭼﭗﺗﺮﻳﻦ ﻣﺤﻠّﻲ را ﻣﺸﺨﺺ‬
‫ﻣﻲﻛﻨﻨﺪ ﻛﻪ ﻣﺘﻌﻠﻖ ﺑﻪ ﻧﻤﺎد ﻓﻌﻠﻲ در ﻓﺎﻳﻞ ورودي اﺳﺖ‪ .‬اﻳﻦ دو ﻣﺘﻐﻴﺮ ﺑﺎﻳﺪ ﺑﻪ ﻃﻮر ﺻﺤﻴﺢ ﺗﻮﺳﻂ ‪ lexer‬ﻣﻘﺪاردﻫﻲ ﺷﻮﻧﺪ‪.‬‬
‫اﻳﻦ ﻣﻘﺎدﻳﺮ ﺳﭙﺲ در ﻧﺎﭘﺎﻳﺎﻧﻪﻫﺎﻳﻲ ﻛﻪ ﻛﺎﻫﺶ ﺑﻪ آﻧﻬﺎ اﻧﺠﺎم ﻣﻲﺷﻮد اﻧﺘﺸﺎر ﻣﻲﻳﺎﺑﻨﺪ‪.‬‬

‫اﮔﺮ ﺑﺨﻮاﻫﻴﻢ ﭼﻨﺪ ﻗﺎﻋﺪه داﺷﺘﻪ ﺑﺎﺷﻴﻢ ﻛﻪ ﺳﻤﺖ ﭼﭗ ﻣﺸﺘﺮﻛﻲ دارﻧﺪ ﻣﻲﺗﻮاﻧﻴﻢ ﺳﻤﺖ راﺳﺖ آﻧﻬﺎ را ﭘﺸﺖ ﺳﺮ ﻫﻢ‬
‫ﺑﻨﻮﻳﺴﻴﻢ و ﺑﺎ "|" ﺑﻴﻦ آﻧﻬﺎ ﻓﺎﺻﻠﻪ ﺑﺪﻫﻴﻢ‪.‬‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫‪action‬ﻫﺎﻳﻲ ﻛﻪ ﺳﻤﺖ راﺳﺖ ﻗﻮاﻋﺪ ﻧﻮﺷﺘﻪ ﻣﻲﺷﻮﻧﺪ )ﺑﺮاي ﻣﺜﺎل ﻣﻴﺎن }‪ ({: ... :‬زﻣﺎﻧﻲ اﺟﺮا ﻣﻲﺷﻮﻧﺪ ﻛﻪ ﺗﺠﺰﻳﻪ‪-‬‬
‫ﮔﺮ ﺑﺨﺸﻲ از ﻗﺎﻋﺪه ﻛﻪ در ﺳﻤﺖ ﭼﭗ آن ‪ action‬ﻗﺮار دارد را ﺷﻨﺎﺳﺎﻳﻲ ﻛﻨﺪ )ﺗﻮﺟﻪ ﺷﻮد ﻛﻪ ‪ scanner‬در اﻳﻦ ﻣﺮﺣﻠﻪ ﺑﺎﻳﺪ‬
‫ﺗﻮﻛﻦ ﺑﻌﺪ از ‪ action‬را ﻧﻴﺰ ﺑﺮﮔﺮداﻧﺪه ﺑﺎﺷﺪ ﭼﻮن ﺗﺠﺰﻳﻪﮔﺮ ﺑﻪ ﻳﻚ ﺗﻮﻛﻦ ‪ lookahead‬اﺿﺎﻓﻲ ﻧﻴﺰ ﺑﺮاي ﺗﺸﺨﻴﺺ ﻧﻴﺎز‬
‫دارد(‪.‬‬

‫ﺑﺮاي ﺗﻌﻴﻴﻦ اوﻟﻮﻳﺖ ﻳﻚ ﻗﺎﻋﺪه ﺑﺪون رﻋﺎﻳﺖ ﻗﺎﻋﺪهي آﺧﺮﻳﻦ ﭘﺎﻳﺎﻧﻪ‪ ،‬ﺑﺎﻳﺪ ﻋﺒﺎرت ﺗﻌﻴﻴﻦ اوﻟﻮﻳﺖ را در اﻧﺘﻬﺎي‬
‫ﻗﺎﻋﺪهي ﺗﻮﻟﻴﺪ ﺑﻴﺎورﻳﻢ‪ .‬ﻣﺜﺎل ﺧﻮﺑﻲ ﺑﺮاي اﻳﻦ ﻛﺎر ﻣﺜﺎل زﻳﺮ اﺳﺖ‪:‬‬

‫;‪precedence left PLUS, MINUS‬‬


‫;‪precedence left TIMES, DIVIDE, MOD‬‬
‫;‪precedence left UMINUS‬‬

‫‪expr ::= MINUS expr:e‬‬


‫}‪{: RESULT = new Integer(0 - e.intValue()); :‬‬
‫‪%prec UMINUS‬‬

‫ﺑﺮﻧﺎﻣﻪي ‪ .3‬ﻣﺜﺎﻟﻲ ﺑﺮاي ﺗﻌﻴﻴﻦ اوﻟﻮﻳﺖ ﻳﻚ ﻋﺒﺎرت ﺑﻪ ﻃﻮر ﺧﺎص‬

‫در اﻳﻨﺠﺎ اﻳﻦ ﻗﺎﻋﺪه ﻃﻮري ﺗﻌﺮﻳﻒ ﺷﺪه ﻛﻪ اوﻟﻮﻳﺖ آن ﺑﻪ اﻧﺪازهي اوﻟﻮﻳﺖ ‪ UMINUS‬اﺳﺖ‪ .‬ﺑﺎ اﺳﺘﻔﺎده از اﻳﻦ ﻗﺎﺑﻠﻴﺖ‬
‫ﺗﺠﺰﻳﻪﮔﺮ ﻣﻲﺗﻮاﻧﺪ ﺑﻪ ﭘﺎﻳﺎﻧﻪي ‪ MINUS‬دو ﻧﻮع اوﻟﻮﻳﺖ ﻣﺨﺘﻠﻒ ﺑﺪﻫﺪ ﺑﻨﺎﺑﺮ اﻳﻨﻜﻪ ﻋﻤﻠﮕﺮ ﺗﻚ ﻋﻤﻠﻮﻧﺪي ﻣﻨﻔﻲ اﺳﺖ و ﻳﺎ‬
‫ﻋﻤﻠﮕﺮ ﺗﻔﺮﻳﻖ اﺳﺖ‪.‬‬

‫‪ .4‬ﺑﺮﺧﻮرد ﺑﺎ ﺧﻄﺎ‬

‫ﻳﻚ وﻳﮋﮔﻲ ﺑﺴﻴﺎر ﺧﻮب ﻛﺎپ ﭘﺸﺘﻴﺒﺎﻧﻲ آن ﺑﺮاي رﻓﻊ ﺧﻄﺎﻫﺎي ﻧﺤﻮي اﺳﺖ‪ .‬ﻛﺎپ ﻫﻤﺎن روش ﺑﺮﺧﻮرد ﺑﺎ ﺧﻄﺎي ‪YACC‬‬
‫را ﺑﻪ ﻛﺎر ﻣﻲﺑﺮد‪ .‬ﺑﻪ ﻃﻮر ﺧﺎص ﻛﺎپ ﻳﻚ ﻧﻤﺎد ﺧﺎص ﺑﻪ ﻧﺎم ‪ error‬را ﭘﺸﺘﻴﺒﺎﻧﻲ ﻣﻲﻛﻨﺪ ﻛﻪ اﻳﻦ ﻧﻤﺎد ﻧﻘـﺶ ﻳـﻚ ﻧﺎﭘﺎﻳﺎﻧـﻪ را‬
‫اﻳﻔﺎ ﻣﻲﻛﻨﺪ ﻛﻪ ﺑﻪ ﺟﺎي اﻳﻨﻜﻪ ﺑﺎ ﻳﻚ ﺳﺮي ﻗﻮاﻋﺪ ﻣﻌﻨﺎ ﭘﻴﺪا ﻛﻨﺪ‪ ،‬ﺑﺎ ﻳﻚ ﻋﺒﺎرت داراي ﺧﻄﺎ ﺗﻄﺒﻴﻖ ﭘﻴﺪا ﺧﻮاﻫﺪ ﻛﺮد‪.‬‬

‫اﻳﻦ ﻧﻤﺎد ﺗﻨﻬﺎ زﻣﺎﻧﻲ ﻧﻘﺶ اﻳﻔﺎ ﻣﻲﻛﻨﺪ ﻛﻪ ﻳﻚ ﺧﻄﺎي ﻧﺤﻮي رخ دﻫﺪ‪ .‬اﮔﺮ ﺧﻄﺎﻳﻲ ﺷﻨﺎﺳﺎﻳﻲ ﺷﻮد ﺗﺠﺰﻳﻪﮔـﺮ ﺳـﻌﻲ‬
‫ﻣﻲﻛﻨﺪ ﺑﺨﺸﻲ از رﺷﺘﻪي ورودي را ﺑﻪ ‪ error‬ﻛﺎﻫﺶ دﻫﺪ و ﺳﭙﺲ ﺑﻪ ﻛﺎر ﺧﻮد اداﻣﻪ دﻫﺪ‪ .‬ﺑﺮاي ﻣﺜﺎل ﻣﻤﻜﻦ اﺳﺖ ﻗﻮاﻋﺪي‬
‫ﺑﻪ ﺷﻜﻞ زﻳﺮ داﺷﺘﻪ ﺑﺎﺷﻴﻢ‪:‬‬

‫| ‪stmt ::= expr SEMI | while_stmt SEMI | if_stmt SEMI | ...‬‬


‫‪error SEMI‬‬
‫;‬

‫اﻳﻦ ﻣﺸﺨﺺ ﻣﻲﻛﻨﺪ ﻛﻪ اﮔﺮ ﻫﻴﭽﻜﺪام از ﻓﺮﻣﻬﺎي ﻋﺎدي ‪ stmt‬ﺑﺎ ورودي ﺗﻄﺎﺑﻖ ﭘﻴﺪا ﻧﻜﺮد ﻳﻚ ﺧﻄﺎي ﻧﺤﻮي ﺷﻨﺎﺳﺎﻳﻲ ﺷﻮد‬
‫و ﺗﻮﻛﻦﻫﺎي داراي ﺧﻄﺎ رد ﺷﻮﻧﺪ ﺗﺎ ﺗﺠﺰﻳﻪ ﺑﺘﻮاﻧﺪ ﺑﺎ ﻳﻚ ";" ﺗﻄﺎﺑﻖ ﭘﻴﺪا ﻛﺮده و اداﻣﻪ داده ﺷﻮد‪ .‬ﻳﻚ ﺧﻄﺎ ﺗﻨﻬﺎ در ﺻـﻮرﺗﻲ‬
‫رﻓﻊ ﻣﻲﺷﻮد ﻛﻪ ﺑﻪ ﺗﻌﺪاد ﻛﺎﻓﻲ از ﺗﻮﻛﻦﻫﺎي ﺑﻌﺪ از آن ﺧﻄﺎ ﺑﻪ ﻃﻮر ﺻﺤﻴﺢ و ﺑﺪون ﺧﻄﺎ ﺗﺠﺰﻳﻪ ﺷﻮد )اﻳـﻦ ﺗﻌـﺪاد ﺗﻮﺳـﻂ‬
‫روال )(‪ error_sync_size‬از ﺗﺠﺰﻳﻪﮔﺮ ﻣﺸﺨﺺ ﻣﻲﺷﻮد و ﻣﻘﺪار ﭘﻴﺶﻓﺮض آن ‪ 3‬اﺳﺖ(‪.‬‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫ﺑﻪ ﻃﻮر ﺧﺎص ﺗﺠﺰﻳﻪﮔﺮ او‪‬ل ﺑﻪ ﻧﺰدﻳﻚﺗﺮﻳﻦ وﺿﻌﻴﺘﻲ در ﺑﺎﻻي ﭘﺸﺘﻪي ﺗﺠﺰﻳﻪ ﻧﮕﺎه ﻣﻲﻛﻨﺪ ﻛﻪ ﺗﺤـﺖ آن ﻳـﺎﻟﻲ ﺑـﺎ‬
‫ﺑﺮﭼﺴﺐ ‪ error‬وﺟﻮد دارد‪ .‬ﭘﺲ از آن ﺗﺠﺰﻳﻪﮔﺮ از روي ﭘﺸﺘﻪ آﻧﻘﺪر ﺑﺮﻣﻲدارد ﺗﺎ ﺑﻪ آن وﺿـﻌﻴﺖ ﺑﺮﺳـﺪ‪ .‬ﺳـﭙﺲ آﻧﻘـﺪر از‬
‫ﺗﻮﻛﻦﻫﺎي ورودي ﺣﺬف ﻣﻲﻛﻨﺪ ﺗﺎ ﺑﺘﻮاﻧﺪ ﺑﻪ ﻛﺎر ﺧﻮد اداﻣﻪ دﻫﺪ‪ .‬ﭘﺲ از ﺣﺬف ﻫﺮ ﺗﻮﻛﻦ ﺗﺠﺰﻳﻪﮔﺮ ﺗﻼش ﻣﻲﻛﻨﺪ ﺗﺎ ﺑـﺪون‬
‫اﺟﺮاي ﻫﻴﭻ ‪action‬اي ورودي را ﺗﺠﺰﻳﻪ ﻛﻨﺪ اﮔﺮ ﺗﻮاﻧﺴﺖ ﺑﻪ ﺗﻌﺪاد ﻛﺎﻓﻲ ﺗﻮﻛﻦ را ﺗﺠﺰﻳـﻪ ﻛﻨـﺪ ﺑـﻪ ﻋﻘـﺐ ﺑﺮﻣـﻲﮔـﺮدد و‬
‫ﺗﺠﺰﻳﻪي ﻋﺎد‪‬ي )ﺑﺎ اﺟﺮاي ‪action‬ﻫﺎ( اﻧﺠﺎم ﻣﻲﺷﻮد‪ .‬اﮔﺮ ﻧﺘﻮاﻧﺴﺖ ﺑﻪ ﺗﻌﺪاد ﻛـﺎﻓﻲ ﺗـﻮﻛﻦ را ﺗﺠﺰﻳـﻪ ﻛﻨـﺪ‪ ،‬ﺗـﻮﻛﻦ دﻳﮕـﺮي‬
‫ﺣﺬف ﻣﻲﺷﻮد و ﺗﺠﺰﻳﻪﮔﺮ دوﺑﺎره ﻫﻤﺎن ﻛﺎرﻫﺎ را اﻧﺠﺎم ﻣﻲدﻫﺪ‪ .‬اﮔﺮ اﻳﻦ ﻛﺎر ﺗﺎ ﭘﺎﻳﺎن ﻓﺎﻳﻞ اداﻣﻪ ﻳﺎﺑﺪ و ﻧﺘﻮاﻧﺪ ﻣﺸﻜﻞ را رﻓﻊ‬
‫ﻛﻨﺪ )و ﻳﺎ وﺿﻌﻴﺖ ﻣﻨﺎﺳﺒﻲ در ﭘﺸﺘﻪ ﻳﺎﻓﺖ ﻧﺸﻮد( رﻓﻊ ﺧﻄﺎ ﺷﻜﺴﺖ ﺧﻮرده اﺳﺖ‪.‬‬

‫‪ .5‬ﺧﻼﺻﻪ و ﻧﺘﻴﺠﻪﮔﻴﺮي‬

‫اﻳﻦ ﻣﺘﻦ ﺑﻪ ﻃﻮر ﺧﻼﺻﻪ ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪﮔﺮ ‪ CUP‬را ﺗﻮﺿﻴﺢ داد‪ .‬ﻛﺎپ ﻃﺮاﺣﻲ ﺷﺪه اﺳﺖ ﺗﺎ ﺟﺎي ﺑﺮﻧﺎﻣـﻪي ﺷـﻨﺎﺧﺘﻪﺷـﺪهي‬
‫‪ YACC‬ﻛﻪ ﺑﻪ زﺑﺎن ‪ C++‬اﺳﺖ را‪ ،‬ﺑﺮاي ﻛﺎرﺑﺮان ﺟﺎوا ﭘﺮ ﻛﻨﺪ‪ .‬اﻃﻼﻋﺎت ﺑﻴﺸﺘﺮ راﺟﻊ ﺑﻪ ﻋﻤﻠﻜﺮد اﻳﻦ ﺑﺮﻧﺎﻣﻪ در ﻛﺪ ﺑﺮﻧﺎﻣﻪ‪-‬‬
‫‪2‬ي ﻛﺎپ ﻳﺎﻓﺖ ﻣﻲﺷﻮد‪.‬‬

‫در ﻣﺠﻤﻮع اﻳﻦ اﺑﺰار ﺑﺮاي ﻛﺴﺎﻧﻲ ﻛﻪ ﻣﻲﺧﻮاﻫﻨﺪ راﺣﺖ و ﺑﻪ زﺑﺎن ﺟﺎوا ‪compiler‬اي ﻃﺮاﺣﻲ ﻛﻨﻨﺪ ﺗﻮﺻﻴﻪ ﻣـﻲ‪-‬‬
‫ﺷﻮد‪.‬‬

‫ﺳﭙﺎﺳﮕﺰاري‬

‫در ﭘﺎﻳﺎن ﻻزم ﻣﻲداﻧﻢ از اﺳﺘﺎد ﺧﻮﺑﻢ ﺟﻨﺎب آﻗﺎي دﻛﺘﺮ ﻛﺎﻇﻢ ﻓﻮﻻدي ﻛﻪ ﻣﻦ را در ﻳـﺎﻓﺘﻦ ﻣﺴـﻴﺮي ﺑـﺮاي اﻧﺠـﺎم ﻣﻄﺎﻟﻌـﺎت‬
‫راﻫﻨﻤﺎﻳﻲ ﻓﺮﻣﻮدﻧﺪ ﺗﺸﻜﺮ ﻓﺮاوان ﺑﻨﻤﺎﻳﻢ‪.‬‬

‫ﻣﺮاﺟﻊ‬
‫‪[1] S.E. Hudson. CUP User’s Manual, Georgia Institute of Technology, 2006.‬‬
‫‪[2] Looking for a parser generator recommendation: Compilers, URL: http://objectmix.com/compilers/36342-‬‬
‫‪looking-parser-generator-recommendation.html#post149459, visited: 6/15/2009.‬‬

‫‪2‬‬
‫‪Source code‬‬
‫ﻣﻮﻟﺪ ﺗﺠﺰﻳﻪ ﮔﺮ ﻛﺎپ‬

‫ﺗﻌﺮﻳﻒ ﻛﺎﻣﭙﺎﻳﻠﺮ‬

‫ﺗﺠﺰﻳﻪﮔﺮ‬ ‫ﺗﻮﻟﻴﺪ ﻛﺪ ﻣﻴﺎﻧﻲ‬

‫ﺷﻜﻞ ‪ .1‬ﻋﻨﻮان ﺷﻜﻞ در اﻳﻨﺠﺎ ﻧﻮﺷﺘﻪ ﻣﻲﺷﻮد‪.‬‬

‫ﺑﺮﺧﻮرد ﺑﺎ ﺧﻄﺎ‬ ‫رﻓﻊ اﺑﻬﺎم از‬


‫ﮔﺮاﻣﺮﻫﺎي ﻣﺒﻬﻢ‬

‫ﺑﺮﻧﺎﻣﻪ‬ ‫ﺟﺎوا‬ ‫‪Lexer‬‬

‫ﺧﺮوﺟﻲ‬

‫ﺷﻜﻞ ‪ .1‬دﻳﺪ ﻛﻠﻲ از ﻋﻤﻠﻜﺮد ﻛﺎپ‬

You might also like