
إنَّ الحمد لله نحمدهُ ونستعينهُ ونستغفره ونستهديهِ ونعوذ باللهِ من شرور أنفسنا وسيئات أعمالنا،
من يهدهِ اللهُ فلا مضلَّ له، ومن يضلل فلا هادي له، وأشهد أن لا إله إلا الله وحده لا شريك له،
وأشهد أنَّ محمداً عبده ورسوله .
تحية طيبة وبعد ..

أقدم لكم هذه المرة مترجم (كومبايلر) لترجمة الأكواد المكتوبة باللغة العربية, أبجد, المشروع أبجد هو بداية لبناء مترجم لتنفيذ كود مكتوب بالعربية قائم بحد ذاته بنسبة 75%
قد يتسائل أحدهم و يقول:" العالم مليء بالمترجمات أشكال و أنواع, مترجمات احترافية تغني عن كل المترجمات الباقية, لم نزعج أنفسنا بمحاولة برمجة مترجم عربي؟!"
سأفول له: "ربما معك حق, و ربما يجب أن لا تتابع قراءة الموضوع و تتضيع وقتك هنا."
صورة للواجهة:

و هذه صورة لتنفيذ الكود المعروض و هو موجود في الملف input.amn:

فقط هناك شيء يجب أن أذكره, تلاحظون أثنا التنفيذ في السطر 7 وجود هذه الجملة "╪ش┘à┘„╪ر_1 =" وهي خربشة غريبة, من المفروض أن يطبع البرنامج هناك, حسب الكود, الجملة"جملة_1 = " و لكن لم أجد خط كونسول يدعم اللغة العربية, لذك هو سيطبع الجمل العربية بتلك الرموز بدل الحروف العربية.
طبعا المترجم يعالج عددا لا بأس به من الأخطاء النحوية و المعنوية مثل إسناد قيمة إلى متغير ليس من نفس النوع و استعمال متغير غير مصرح عنه....الخ
للأسف و لضيق الوقت لا يوجد هناك شرح لقواعد الكود العربي المستخدم, ولكنه مشابه إلى حد بعيد سانتاكس السي بلس بلس, على كل حال كل ما يقوم المترجم بترجمته حاليا موجود في الملف input.amn.
لن أقوم بشرح كيفة عمل الكومبايلر لأنني قمت بهذا في موضوع سابق, في المقابل سأشرح مستلزمات إعادة بناء البرنامج من السورس كود.
ككل مترجم, بغض النظر عن الواجهة, يتكون مترجمنا هذا من المحلل المعجمي و المحلل النحوي و المحلل المعنوي و مولد الكود, تم بناء هؤلاء باستخدام الـ Bison و الـ Quex, في المرة الماضية استخدمنا الـ Bison و الـ FLEX, لكن و بما أن الـ FLEX لا يدعم اللغة العربية أو الترميز UTF بصفة عامة سنستخدم الـ Quex, كيف نثبتهما؟
لتثبيت الـ Bison, حمل البايزون من الرابط التالي:
بعد ذلك قم بتنصيبه على المسار C:\Program Files\bison:

ثم التالي..التالي..إلى غاية انتهاء التثبيت.
بعد انتهاء التثبيت انسخ محتوى المجلد C:\Program Files\bison\bin الى المجلد C:\WINDOWS, و بهذا ينتهي إعداد البايزون.
لتثبيت الـ Quex, أولا حمل الـ Quex من الرابط التالي:
الـ Quex يحتاج إلى البايثون للعمل لذلك حمل البايثون 2.7 من هذا الرابط:
إذا قمت بتحميله من موقع آخر فتأكد أن تحمل النسخة 2.x, يوجد نسخ بعدها لكن الـ Quex لا يعمل عليها.
قم بتنصيب البايثون على المسار C:\WINDOWS :

و أكمل التنصيب.
الآن, يجب ان ننصب الـ Quex على أي مسار, مثلا C:\quex, و بهذا ننهي اعداد بيئة العمل.
افتح المشروع باستخدام الفجوال ستديو و ستجد هناك مشروعين, الأول باسم ANAA و هو الواجهة, أما الثاني فباسم arabicParserCodec و هو مشروع المترجم.
الأهم هنا هو المشروع arabicParserCodec.
من خلال النافذة Solution Explorer الواقعة على اليمين نستطيع أن نرى الملفات المكونة للمشروع:

الملف arabicLexer.qx يمثل وصف السكانر و هو الوصف الذي سنمرره الـ Quex حتى يولد كود السكانر.
الملف arabicParser.ypp به وصف البارسر الذي سنمرره للبايزون لتوليد كود البارسر.
الملف input.txt هو الكود المكتوب باللغة العربية و الذي سيقوم البرنامج الناتج بفحصه و التأكد من صحة نحوه.
أما الملف main.cpp فهو الملف الرئيسي في البرنامج.
يمكنك فتح و تعديل الملفات باستخدام محرر الفيجوال ستيديو.
ملاحظة:نوع ترميز الملف arabicLexer.qx هو :
Unicode (UTF-8 without signature)-codepage 65001 لذلك و إذا أردت إنشاء ملف جديد به وصف جديد للسكانر و استعملت فيه اللغة العربية أو أي لغة non-ascii قم بحفظه بذلك الترميز + لا تستخدم مفكرة الويندوز, محرر الفيجوال ستديو أفضل.
يجب أولا أن نعالج ملف وصف البارسر arabicParser.ypp باستخدام البايزون و لنفعل هذا ندخل هذا الأمر في نافذة الدوس:
bison -d arabicParser.ypp
هذا الأمر مكتوب في الملف parser.bat لذلك دوبل كليك على هذا الملف و سيتم الأمر لنلاحظ أن البايزون سيولد لنا سورس البارسر.
أما لمعالجة ملف وصف السكانر arabicLexer.qx فيكفي دوبل كليك على الملف txt_lexer.bat
الذي به الامر اللازم لفعل ذلك, في حالة وجود أخطاء في الوصف سيولد الـ Quex كود السكانر, أما في حالة وجود أخطاء تفقد الملف الناتج من الكيوكس t.txt لتجد لائحة الأخطاء.
قبل بناء السورس إذهب إلى project à <projectname> properties في آخر القائمة ثم افتح "Configuration properties" à "C/C++" à "General" :

و تأكد من أن المسار الموجود في additional include directories هو نفسه المسار المثبت عليه الـ Quex في جهازك.

بالنسبة للكود, سأشرح أصعب قطعة لمن يهمه مثل هذه المشاريع, سأشرح مرحلتي التحليل المعنوي و توليد الكود, أول شيء يذكر في هذه المرحلة هو جدول الرموز(symbol table).
نفتح الملف arabicParser.ypp و أول شيء نضيفه و #include "SymbolTable.h" , و هو الملف الرأسي لتعريف جدول الرموز, نفتح الملف SymbolTable.h و SymbolTable.cpp....
جدول الرموز سنمثله بالكلاس CSymbolTable و هي معرفة في الملفين السابقين.
على مستوى ملف التعريفات SymbolTable.h , أول ما يصادفنا هذه الأسطر:
#define _ident -2 #define _endl -1 #define _void 0 #define _int 1 #define _float 2 #define _str 3 #define _chr 4 #define _int_tab 5 #define _float_tab 6 #define _str_tab 7 #define _chr_tab 8
هي مجرد تعريفات لرموز سنستعملها لنحدد بعض الأنواع, مثلا _int عرفناه على أنه القيمة 1, لا تهمنا القيمة في حد ذاتها فقط عوض أن نمثل النوع الصحيح بـ 1 سنمثله بـ _int.
هذا السطر
extern std::ofstream fresult;
سنتركه فيما بعد.
بعد ذلك نعرف هذه التركيبة:
typedef struct {
std::string name;
char inCodeSymbolName[128];
int type;
int max_index;
bool isConst;
}stEle;
التركيبة stEle و هي تركيبة تمثل كل رمز تم التصريح عنه في الكود العربي مهما كان نوعه, الحقل name سيحمل الاسم العربي لذلك الرمز, الرمز inCodeSymbolName سنضع فيه الاسم الانكليزي الموافق للاسم العربي حتى نستعمله في كود الاسمبلي الناتج, مثلا المتغير س لا سنسميه var0 في كود الاسمبلي و المتغير ع نعطيه الاسم var1 و هكذا...
بعد طلك نعرف الكلاس التي تمثل جدول الرموز و ستكون عبارة عن قائمة من التركيبات السابقة:
class CSymbolTable: public std::list<stEle>{
public:
int current_type;
private:
int inCodeSymbolNameCounter;
public:
CSymbolTable(void);
~CSymbolTable(void);
stEle* operator [] (int) const;
stEle* operator [] (std::string) const;
void putSym(std::string, int, int, bool);
};الكلاس CSymbolTable سترث من الكلاس std::list<stEle> و نضيف إليها أيضا بعض المتغيرات و الدوال.
المتغير current_type يحمل قيمة النوع الذي نتعامل معه حاليا, سنستعمله لاحقا أثناء التصريح عن المتغيرات, المتغير inCodeSymbolNameCounter عبارة عن عداد نستعمله أثناء إعطاء الاسم الانكليزي الموافق لكل متغير, var0, var1, ...
الدالتين CSymbolTable(void); و ~CSymbolTable(void); هما الباني و الهادم للكلاس, بعد ذلك نعرف الاوبيراتور [] و الذي سنستعمله للاستخراج بنية المتغير بناءا على اسمه, مثلا:
ST["س"]. inCodeSymbolName
أما الدالة putSym فهي مسؤولة عن تثبيت رمز جديد داخل جدول الرموز.
تعريفات الدوال السابقة لابد لها من جسم أو كود الدالة, ذلك موجود في الملف SymbolTable.cpp, الكود الموجود به يتطلب معرفة باللغة C++ لفهمه.
بعد تعريف جدول الرموز لابد من استعماله, نعود الى الملف arabicParser.ypp, في السطر 24 نصرح عن جدول الرموز من خلال:
CSymbolTable ST;
قبل أن نستعمله يجب أن ألفت انتباهكم الى تغيير صغير هنا:
%union
{
struct{
int type;
int isVariable;
std::string *str;
}TYPE;
};رأينا ذلك من قبل, فقط غيرنا في اليونيون بتعريفنا لتركيبة بداخله بها 3 حقول سنستعملها فيما بعد.
نعود لجدول الرموز, على مستوى السطر 26 نعرف الدالة التالية:
setupSym(std::string sym_name,int buffer_size,bool isConst)
تلك الدالة سنستعملها لتثبيت المتغير في جدول الرموز, طبعا عرفنا من قبل داخل الكلاس CSymbolTable تقوم بتثبيت الرمز داخل جدول الرموز, الفرق هو أن الدالة التي نعرفها الآن ستقوم بالتحقق من الرمز قبل تثبيته و اذا كان هناك خطأ نعرضه.
البارمترات التي نمررها للدالة ثلاث, الأول اسم المتغير بالعربي, الثاني حجم المصفوفة في حالة كون المتغير عبارة عن مصفوفة و الثالث نوع المتغير.
قبل تثبيت الرمز في جدول الرموز لابد من اجراء سلسلة من التحقيقات أولها هذا:
if((ST.current_type>4)&&(buffer_size<1))
ماذا نريد أن نقول هناك؟ إذا كان النوع الذي نوع المتغير أكبر من 4 (حسب تعريفات الانواع في الملف SymbolTable.h نجد أن الأنواع الاكبر من 4 هي مصفوفات) أو بعبارة اخرى اذا كان المتغير عبارة عن مصفوفة, و حجمه اصغر من 1 سنعرض رسالة الخطأ هذه:
خطأ: حجم المصفوفة يجب أن يكون أكبر من 0 :
من خلال:
errorsNumber++; fresult<<"ط®ط·ط£: طط¬ظ… ط§ظ„ظ…طµظپظˆظپط© ظٹط¬ط¨ ط£ظ† ظٹظƒظˆظ† ط£ظƒط¨ط± ظ…ظ† 0 : ط³ط·ط± "<<line_number<<std::endl;
مع زيادة قيمة errorsNumber بواحد, لم نقم بعرض تلك الرسالة على الشاشة بل قمنا باخراجها في الملف المحدد بالمتغير fresult , هذا الملف نقوم بفتحه فيالدالة main() في الملف main.cpp.
تلاحظون أن رسالة الخطأ أخرجناها في الملف برموز غريبة !!!! اضطررت لذلك حتى يكون نوع الملف UTF-8 و الرموز ستتحول الى جملة عربية مفهومة في الملف.
ذلك أول خطأ معنوي تم الكشف عنه و بذلك تكون الصورة اتضحت أكثر فأكثر, نواصل.
اذا لم نقع في ذلك الخطأ فنتأكد من أن ذلك الرمز غير معرف من قبل باستعمال:
if(ST[sym_name]==NULL) ,
تتذكرون الاوبيراتور [] الذي عرفناه في كلاس جدول الرموز من قبل, ذلك الاوبيراتور سيعيد NULL اذا لم يجد الرمز في جدول الرموز.
لذلك, اذا لم يكن الكود موجودا من قبل نثبته في جدول الرموز في السطر 35:
ST.putSym(sym_name,buffer_size,line_number, isConst);
الأسطر 36 الى 50 تابعة لعملية توليد الكود.
طيب في حالة المتغير معرف من قبل؟ نعرض رسالة الخطأ:
"خطأ : المتغير "<<sym_name<<" معرف مسبقا : سطر "
طبعا باستعمال الرموز الغريبة على مستوى السطر 60.
بعد ذلك نعرف ابتداءا من السطر 67 الدالة symCheck , بسيطة, بواسطتها نتأكد من وجود المتغير قبل استعماله, مثلا قبل كل عملية اسناد او اخراج يجب ان نتأكد من أن المتغير معرف مسبقا و الا نعرض رسالة الخطأ:
"خطأ : متغير غير معروف "<<sym_name<<" : سطر "
ابتداءا من الآن كل ما سنفعله هو استعمال الدوال السابقة, مثلا لنحلل عملية تعريف متغير...
تعريف المتغيرات يبدأ من القاعدة:
var_declaration: declaration_type vars TKN_SEMICOLON ;
عندما ندخل الى القاعدة declaration_type سنحتفظ بالنوع الحالي:
declaration_type:TKN_INT {ST.current_type=_int;}
| TKN_STRING {
ST.current_type=_str;
}
| TKN_CHAR {ST.current_type=_chr;}
| TKN_VOID {ST.current_type=_void;}
;نحفظ النوع الحالي في ST.current_type و عند خروجنا سنبقى محتفظين بالنوع الحالي أثناء تجولنا داخل القاعدة vars :
vars: TKN_IDENTIFIER {
setupSym(*$1.str,0, false);
} VarInitialisation
...نقوم باستدعاء الدالة التي تعالج المتغير اما تثبته او تعرض ريالة خطأ و نمرر للدالة اسم المتغير الموجود في *$1.str
حسنا كانت هذه مقدمة...انطلاقا من هنا راجعوا باقي الكود و أي شيء غير مفهوم سنناقشه بإذن الله.

توليد الكود يحتاج الى ان نعرف كلاس ثانية لتنظيم العملية, اكلاس CCodeGenerator و هي معرفة في الملفين CodeGenerator.h و CodeGenerator.cpp مثل الكلاس السابقة.
الكلاس بسيطة جدا, اولا نلقي نظرة على الملف الرأسي CodeGenerator.h, أول تعريف هو تعريف للعمليات :
#define NONE 0 #define PRINT 1 #define SCAN 2 #define AFFECT 3 #define PLUS 2 #define MINUS 3
واضح جدا, أهم العناصر في الكلاس:
public:
std::ofstream fcode;
char constSection[1024];
char dataSection[1024];
char varsSection[1024];
char codeSection[8192];
int Operation;
int mathOperation;الملف fcode هو الملف الذي سنكتب فيه كود الاسمبلي النتيجة, constSection هي الجزء المخصص لتعريف الثوابت في الكود و codeSection هو الجزء الخاص بالعمليات, طبعا سنكتب في كل جزء بعض اكواد الاسمبلي و في الاخير نكتب كل الاجزاء في الملف.
المتغير Operation سيحمل العملية الحالية, اما قراءة او كتابة او اسناد, المتغير mathOperation سيحمل العملية الرياضية الحالية, جمع او طرح او ...
المتغيرات الخاصة :
private:
int _msg_x;
int _label_x;
char* GenStrIdent();
char* GenLabel(char *);المؤشر _msg_x هو عداد لتوليد اسماء الثوابت التي سنخصصها للسلاسل الحرفية المستعملة في الكود العربي, مثلا اذا كان هناك هذا السطر:
جملة = "مرحبا..." ;
في كود الاسمبلي لا نستطيع اسناد سلسلة حرفية مباشرة بل يجب ان نعرفها هكذا:
_msg0_ db " مرحبا...",0
استعملنا المتغير _msg0_ و في جملة اخرى سنستعمل _msg1_ و هكذا لذلك سنحتاج الى عداد.
نفس الشيء بالنسبة للمتغير _label_x وهو عداد للـ labels المستعملين في الحلقات.
بعد ذلك نجد تعريف بعض الدوال :
public:
CCodeGenerator(void);
~CCodeGenerator(void);
void initCode(char *);
void addConst(char *);
void addData(char *);
void addVar(char *);
void addCode(char *);
void disposeCode();
void generateCode(std::string str, ...);الدالة initCode نقوم فيها بفتح ملف كود الاسمبلي و الدالة disposeCode نقوم فيها بكتابة أجزاء كود الاسمبلي داخل الملف ثم نغلقه.
فهم مرحلة توليد الكود يعتمد على مدى فهمكم للغة الهدف و هي لغة الاسمبلي.
الدالة generateCode هي الدالة الرئيسية المسؤولة عن توليد الكود حسب اسم المتغير الممرر اليها على شكل بارمتر + مجموعة من المتغيرات التي تختلف في نوعها في كل مرة.
داخل الدالة generateCode في الملف CodeGenerator.cpp نجد عملية كتابة كود الاسمبلي الموافق لكل عملية حسب نوع المتغير.
بعد ذلك نضمن الملف الرأسي ونصرح عن الكلاس في arabicParser.ypp:
CCodeGenerator codeGenerator;
مثلا, في السطر 36 و بعد تثبيت المتغير المصرح عنه نقوم بتوليد كود الاسمبلي الموافق له, مثلا اذا كان نوع المتغير صحيح فاننا نضيف لكود الاسمبلي:
Var0 dd 6 dup( ?)
وهذا من خلال :
if(ST.current_type == _int)
{
codeGenerator.addVar(ST[sym_name]->inCodeSymbolName);
codeGenerator.addVar("\tdd\t6 dup(?)\n");
}كما قلت من قبل, فهم هذه المرحة يتطلب معرفة بأساسيات لغة الاسمبلي.

لتحميل المترجم:
لتحميل السورس كود:
