371 likes | 550 Views
Code Generation in ANTLR. By: Amir Mehrabi J. Instructor: Dr.Parsa IUST – Computer Department :: Jan 2008. فهرست:. Action ها در ANTLR توضیح کوتاهی در مورد بعضی از خواص Action ها تولید کد 3 آدرسه گرامر نمونه ساختار HashMap Action های مربوط به تولید کد 3 آدرسه
E N D
Code Generation in ANTLR By: Amir Mehrabi J. Instructor: Dr.Parsa IUST – Computer Department :: Jan 2008
فهرست: • Action ها در ANTLR • توضیح کوتاهی در مورد بعضی از خواص Action ها • تولید کد 3 آدرسه • گرامر نمونه • ساختار HashMap • Action های مربوط به تولید کد 3 آدرسه • بررسی قسمت های مختلف گرامر • تولید کد • منابع
Actionها در ANTLR • در ANTLR یکی از قابلیت های مهمی که شاید در معروف شدن آن نقش مهم و بسازایی ایفا کرده است قابلیت Action نویسی در آن می باشد که انعطاف پذیری بسیاری به آن می دهد. • با استفاده از این خاصیت هنگام پارس کردن جملات ورودی (Parsing) بر طبق گرامر معرفی شده می توان عملیاتی را که برای آن تعریف می کنیم انجام دهیم.
توضیح کوتاهی در مورد بعضی از خواص Action ها: • برای اینکه بتوانیم کد 3 آدرسه در ANTLR ایجاد کنیم ابتدا نیاز است که به برخی از خواص Action ها اشاره کنیم: • معمولا کد ها را باید در بین گرامر ها جا داد که به آن ها Action می گویند. Action ها معمولا مستقیما بر روی ورودی عمل می کنند اما همچنین آنها می توانند متد های دیگر را فراخوانی کنند تا بتواند بصورت دقیق عملیاتی را که در خارج از متن گرامر تعریف شده اند را انجام دهد. • Action ها بصورت بلاکهای (BLOCK) متنی نوشته می شوند که بین دو علامت ”{“ و ”}“ محصور می شوند.
شناساگر متن (Recognizer) در ANTLR با توجه موقعیت Action ها که در کدام محل قرار دارند (با توجه به موقعیت آنها در بین گرامرها) آنها را اجرا می کند. برای مثال پارسر هنگامی که با توجه به ورودی ها به قانون (RULE) زیر برسد خروجی “Found a decl”را در قسمت خروجی (Output) تولید می کند: پنجره خروجی ANTLR در قسمت سمت چپ – پایین صفحه نرم افزار ANTLRWORK قرار دارد که در صفحه بعد می بینید.
ANTLR برای اینکه بتواند کارایی ACTION ها را بیشتر کند این امکان را می دهد که شما بتوانید عملیات ACTION ها را برروی ورودی ها اعمال کند و خروجی مناسب را با توجه به ورودی ها تولید نماید. در عملیات ACTION ها می توان از TOKEN های ورودی و همچنین قوانین نیز استفاده کرد. برای مثال می توان از RULE ها همانطور که در شکل زیر نشان داده شده است استفاده کرد: • همانطور که در بالا می بینید 2 قانون در بالا به نام های decl و type تعریف شده و یک قانون از نوع Lexer یعنی ID تعریف شده است که برای تعریف نام شناسه ها استفاده می شود بعد از اینکه پارسر توانست قانون decl را با موفقیت شناسایی کند خروجی زیر را که به فرم پاسکال است تولید می کند: var i : int;
در این ACTION عبارت text یکی از مشخصات از پیش تعریف شده در ANTLR می باشد . • عبارت $ID یک اشاره گر به TOKEN ورودی می باشد که مربوط با قانون Lexer برای تولید شناسه می باشد. • عبارت $type یک اشاره گر به قانون است که برای تولید عبارت int یا float استفاده می شود.
اگر از بعضی از قوانین بصورت تکراری در قانون دیگر استفاده شود برای جلوگیری از اشتباه در ANTLR باید ابتدا آنها را نامگذاری کرد(برچسب گذاری کرد) سپس از نام(برچسب) آنها در داخل ACTION استفاده کرد. استفاده تکراری از قانون ID: در اینجا ابتدا آنها را نامگذاری می کنیم سپس از نام آنها در متن ACTION استفاده می کنیم.
تولید کد : • در این قسمت با استفاده از خواص Action های ذکر شده اقدام به تولید کد 3 آدرسه می نمائیم . البته در این قسمت از Action ها بصورت پیشرفته تری استفاده می نمائیم که در جلوتر در توضیح می دهیم. • برای تولید کد 3 آدرسه ابتدا نیاز به یک گرامر برای گرفتن ورودی د اریم تا ورودی با توجه به گرامر ذکر شده توسط پارسر چک شود و در حین اینکار کد 3 آدرسه تولید شود.
گرامر نمونه: • ابتدا ما می خواهیم یک گرامر داشته باشیم تا بتوانیم یکسری عبارت های ریاضی را گرفته و عملیات ریاضی مانند جمع ، ضرب ، تقسیم و تفریق و عملیاتی مانند اولویت را اعمال کند برای مثال عبارت 2+4*(12/3) را از ورودی گرفته و برای آن کد 3 آدرسه تولید نماید و حاصل عبارت را محاسبه و نگهداری کند همچنین باید قابلیت آدرس دهی را داشته باشد مانند اینکه اگر حاصل عبارت اولیه را حساب کرده و در یک متغییر قراردادیم در جای دیگر برنامه بتوانیم از این متغییر استفاده کنیم. • فرضی که در اینجا در نظر گرفته این تولید که بصورت کد میانی است که وابستگی و محدودیتی در استفاده از رجیستر ها وجود ندارد
// Grammar Name grammar t; // First Rule that ANTLR Start Here // Our program consist of STAT(s) (from 1 to …. State) prog: (stat)+ ; // Each stat can have 3 alternative // 1- expr (e.g. 2876/4) // 2- ID = expr (e.g. a=2*4) // 3-BLANK stat : exprNEWLINE | ID '=' exprNEWLINE | NEWLINE ; // Each expr can right recursive with 2 alternative // e.g. expr=texpr + expr + expr +expr – expr …. expr : texpr (('+' expr | '-' expr ))* ; // Each texpr can generate 2 alternative texpr : atom (('*' texpr |'/' texpr ))* ; // Each atom consist of 3 lexer rule atom : INT | ID //variable reference | '(' expr ')' ; // Lexer Rules ID : ('a'..'z' |'A'..'Z' )+ ; INT : '0'..'9' + ; NEWLINE : '\r' ? '\n' ? ';' ; WS : (' ' |'\t' |'\n' |'\r' )+ {skip();} ;
اکنون برای تولید کد 3 آدرسه کم کم Action ها را به گرامر اضافه می کنیم .در ابتدا ما احتیاج به یک ساختار داده ای برای حفظ و نگهداری بخشی از کد ها در حافظه داریم برای اینکار در ابتدای گرامر عبارت زیر را اضافه می کنیم: • در قسمت اول (header) برای استفاده از توابع کتابخانه ای جاوا از عبارت • import java.util.HashMap; • استفاده می کنیم. در قسمت دوم دو متغییر عمومی (Global) تعریف می کنیم که نوع آن HashMap می باشد که متشکل از یک ساختار داده ای است دارای متد هایی مانند put (برای افزودن در لیست) ، Get ( برای فراخوانی از لیست ) و ... می باشد که در جلوتر توضیح داده می شود.
هم اکنون می خواهیم برای قانون Stat شروع به نوشتن Action بکنیم به شرح زیر: • با استفاده از این Action قصد داریم که بعد از اینکه با توجه به ورودی های داده شده پارسر موفق به شناسایی قانون expr شد. حاصل عبارت در خروجی چاپ شود. به عنوان مثال بعد از اینکه عبارت 2*4 در ورودی دیده شد و بعد از آن یکی از حروفی که در قانون NEWLINE آمده است دیده شد حاصل قانون expr (در اینجا یعنی 6)را در خروجی چاپ می کند.
اکنون می خواهیم ببینیم چگونه می توان حاصل عبارت را محاسبه کرد . در ابتدا برای عبارت هایی که دارای جمع (+) یا تفریق (-) می باشند Action می نویسیم: • برای قسمت اول خروجی قانون expr همان خروجی قانون texpr است. • برای اینکار پس باید نوع خروجی تابع expr را مشخص کنیم که با returns [int value] مشخص شده است. • در این قسمت همانگونه که مشخص است از یکی از مشخصه های از پیش تعریف شده ANTLR به نام value استفاده کردیم که اشاره گری به خروجی قانون expr است. • همچنین برای مشخص شدن نوع خروجی expr ها در قانون آنها را نامگذاری کردیم.
بصورت مشابه برای قانون texpr نیز Action ها را می نویسیم. • در اینجا نیز خروجی قانون (rule)texpr را مشخص کردیم که بصورت عدد صحیح می باشد (int)
اکنون بهتر است که کمی به ابتدای گرامر برگردیم . • برمی گردیم به قانون stat تا کمی آن را تغییر دهیم. • همانطور که می بینید در قسمت دوم قانون ، Action آن را بدین صورت می نویسیم و در واقع قصدمان این است تا نتیجه عبارت را در حافظه ذخیره کنیم و برای اینکار از همان ساختار داده ای که در ابتدای برنامه تعریف کردیم استفاده می کنیم.
ساختار HashMap • ساختار HashMap بصورت یک جدول 2 ستونه است که یک ستون آن به عنوان کلید (Key) و ستون دیگر آن به عنوان مقدار (Value) شناخته می شود و دارای متد های مانند PUT ، GET و... می باشد که در واقع interface این ساختار داده ای به حساب می آیند.به عنوان مثال در Action فرضی بعد از اجرای دستور memory.put($ID.text,3) ساختار HashMap بصورت زیر تغییر می کند. Memory.put($ID.text,3) در اینجا فرض شده است مقدار $ID.text برابر با a باشد نکته قابل توجه در این ساختار این است که HashMap تکراری بودن کلید ها را چک می کنید یعنی اگر در ستون کلید یک مقدار تکراری وارد شود مکانیزم جبرانی برای آن در نظر می گیرد.
1 2 3 • حالا دوباره بر می گردیم به گرامر و Action بخش آخر را کامل می کنیم. • در این قسمت ابتدا خروجی قانون atom را مشخص می کنیم که از نوع عدد صحیح است. • 1- سپس در گام اول برای قسمت اول این قانون Token شناسایی شده از ورودی را که پارسر با توجه به قانون INT تشخیص داده است تبدیل به مقدار عددی آن می کنیم و به عنوان خروجی بر می گردانیم. • 2- در گام بعدی به سراغ قسمت دوم این قانون می رویم که پارسر در ورودی به یک متغیر برخورده است برای تعیین خروجی ابتدا باید ببینیم که مقدار عددی آن را قبلا محاسبه کردیم یا نه اگر محاسبه کرده باشیم مقدار عددی آن را در حافظه نگهداری کرده ایم پس با یک واکشی اطلاعات از ساختار داده ای خودمان مقدار آن را بر می گردانیم و تبدیل به عدد کرده و به عنوان خروجی این قانون بر می گردانیم چنانچه در ساختار داده ای مقدر آن پیدا نشد به معنی آن است که آن یک متغییر ناشناخته است و باید خطایی در خروجی برای کاربر ظاهر شود • 3- در قسمت سوم مقدار عبارت داخل پرانتز به عنوان خروجی این قسمت استفاده می شود.
بعد از این قسمت ما موفق شدیم گرامر را طوری تغییر دهیم که بتواند عملیات مورد نظر را انجام دهد بعد از این مرحله کافیست در هر قسمت که عملیاتی انجام می شود خروجی مناسب بصورت کد 3 آدرسه تولید شود قبل از اینکار بهتر است ابتدا نگاهی کامل به گرامر بیندازیم.
اکنون گرامر ما آماده است تا در نقاط خاصی خروجی را طوری تولید کنیم که تبدیل به کد 3 آدرسه بشود این نقاط نقاطی است که ما عملیات ریاضی مانند جمع معادل ADD ، تقریق معادل SUB ، ضرب معادل MUL عمل تقسیم معادل DIV ، عمل فراخوانی از حافظه معادل LD ، عمل ذخیره کردن در حافظه و ... را انجام می دهیم حال با نگاهی مجدد به گرامر این نقاط را مشخص می کنیم سپس ACTION مربوط به تولید کد 3 آدرسه را در این نقاط اضافه می کنیم.
1 2 3 4 5 6
اکنون با تشخیص بعضی از نقاط مشخص که باید در این نقاط خروجی مناسب برای تولید کد 3 آدرسه ، اقدام به اضافه نمودن Action مناسب می کنیم.
ابتدا در اولین نقطه که قصد ذخیره کردن مقدار یک متغیر برای استفاده بعدی را داریم Action مناسب را اضافه می کنیم. با اضافه کردن این Action هرگاه که قصد ذخیره کردن مقدار متغیر در حافظه را داریم دستورالعمل معادل آن را تولید می کنیم.
توجه: • دستورالعمل هایی که ما در اینجا قصد تولید کردن آن را داریم بصورت operand opr1,opr2 می باشد .که op بر روی آنها عمل می شود و حاصل در opr1 قرار می گیرد. برای همین ما در قوانین (Rule)علاوه بر خروجی قانون احتیاج به نام رجیستری که به عنوان خروجی این عملیات استفاده می شود نیز داریم. برای این منظور کمی گرامر را باید تغییر دهیم که تغییرات را در صفحه بعد می بینید.
حالا با تغییراتی که دادیم می توانیم بقیه Action ها را به گرامر اضافه کنیم. ابتدا به سراغ قانون expr می رویم. • همانگونه که می بینید در قسمت اول برای جمع عبارت ها ابتدا دستورالعمل ADD را اضافه می کنیم همانطور که مشخص است آرگومان های دستور println از برچسب هایی است که قبلا معرفی شده اند برچسب a به عبارت expr اولی اشاره می کند و برچسب b به عبارت expr دوم اشاره می کند سپس با استفاده از Action که بصورت $nm=$a.nm مشخص می کنیم که نام خروجی (نام رجیستر خروجی که عملیات در آن ذخیره می شود) همان نام opr1 می باشد. • اکنون ما توانستیم برای عملیات جمع و تفریق دستورالعمل های مناسب مطابق با oprand opr1 , opr2 را تولید کنیم.
حالا نوبت به عملیات ضرب و تقسیم می رسد که به روشی مشابه این کار را انجام می دهیم.
حالا نوبت به قانون atom می رسد این قانون شامل 3 قسمت می باشد: • قسمت اول آن هنگامی است که Token ورودی با توجه به قانون INT شناسایی شده است در این قسمت ما در بخش دستورالعمل احتیاج داریم که عدد خوانده شده از ورودی را در یک رجیستر ذخیره کنیم که Action مناسب با آن در زیر آمده است که با یک دستور MOV همراه خواهد بود 1
در قسمت دوم ما ابتدا مقدار شناسه را از حافظه فراخوانی می کنیم ، همچنین نام رجیستری را که مقدار این متغیر در آن ذخیره شده سپس نام این رجیستر را به عنوان خروجی این قانون استفاده می کنیم. • در قسمت سوم چون عملیات خاصی استفاده نشده احتیاج به Actionخاصی نداریم. 2 3
اکنون گرامر ما کامل شده بهتر است نتیجه خروجی را با یک داده ورودی واقعی تست کنیم. برای نمونه داده ورودی به بصورت زیر در نظر می گیریم : A=3*4/(3+10); B=A*2; C=A+B; حال می خواهیم نتیجه خروجی را مشاهده کنیم.
همانگونه که در شکل می بینیم ما موفق شدیم دستورالعمل های معادل عبارت ورودی را تولید کنیم.
The Definitive ANTLR Reference , May 2007 ,Terence Parr منابع: