السلام عليكم ورحمه الله وبركاته
اهلا بالشباب وكل اعضاء المنتدى الكرام حبيت اليوم أكتب لكم عن موضوع شيق وممتع للغاية وسأوضح
فيه ان شاء الله فكرة من افكار عمل Assembler طبعا هناك فرق ما بين
Assembler
تعريفه :-
الأسمبلر هو اسم الmodule الذي يقبل مدخل(input) من لغة الAssembly و يُنتج (output) مُخرج من لغة الإلة Machine Code و في العادة يكون تصميم الأسمبلر بسيط فهو مجرد Line by Line Translation أي مترجم سطري و هذا لطبيعة الAssembly البسيطة فهو يقوم بعمل Recognition للأوامر (mnemonics) و تحويلها الى الmachine code المناظر لها متبوعاً بالقيم التي تليه
بالطبع الAssemblers الأحدث أكثر تعقيداً و لكن مازالت الفكرة الأساسية واحدة...
وكان تطوير لغات المجمّع أحد أول الاختراعات الهامة في تاريخ اللغات البرمجية. وقد كانت هذه اللغات أول أداة ساعدت المبرمجين على عناء التعامل مع الأصفار والواحدات، ومكنتهم من استخدام تسميات برمجية ذات دلالة خاصة بالتعليمات والإجراءات. وكان أول مجمّع عبارة عن نظام يقوم بتمثيل تعليمات لغة الآلة برموز بسيطة Mnemonic يسهل تذكرها
كانت البرامج المكتوبة بلغة المجمّع أكثر قابلية للقراءة من الشيفرة الثنائية، على أنه كان ينبغي ترجمتها إلى لغة الآلة باستخدام برنامج حاسوبي خاص يدعى بالمجمّع.
يقبل المجمّع برنامجاً مكتوباً بلغته ويعتبرها معطيات، ويحول رموز Opcode إلى مكافئاتها الرقمية، ويربط العناوين الرمزية بمواضع الذاكرة، ومن ثم يقوم بتوليد برنامج بلغة الآلة. يدعى برنامج لغة المجمّع بالبرنامج المصدري أو Source Program، يدعى البرنامج النهائي المكتوب بلغة الآلة بالشيفرة العنصر أو Object Program.
وبما أن تعليمة واحدة من تعليمات لغة المجمّع تولد تعليمة واحدة أو عنصر معطيات وحيد، لذا فقد عرفت لغة المجمّع كلغة برمجية من مستوى "نظير-لنظير". وبنتيجة ذلك، تستند المجمعّات على الآلة أو الجهاز، ويتم توليد هذه الشيفرات الخاصة بنوع محدد من الحواسب
ولليوم، لاتزال عملية البرمجة بلغة المجمّع هي أساس بعض المهام البرمجية. فمثلا، يمكنكم زيادة سرعة البرنامج بالدخول إلى السجلات الداخلية مباشرة، عوضاً عن الدخول إلى مواضع الذاكرة التي تعتبر أبطأ. هذا، وإن معظم اللغات البرمجية عالية المستوى لاتتيح للمبرمج بهذا المستوى من التحكم
ميزات لغة المجمّع:
- تعطى جميع العمليات أسماء ذات دلالة أو رمز Opcode يسهل تذكره، مثل ADD؛
- يتم استخدام تسيمات رمزية لكافة المعطيات وعناوين الذاكرة، الأمر الذي يسهل على المبرمج مهمة تخصيص أماكن التخزين في الذاكرة؛
- تخفض من نسبة الأخطاء الطباعية من خلال استخدام العناوين الرمزية؛
- من الأسهل قراءة وفهم البرنامج المكتوب بلغة المجمّع.
مساوئ لغة المجمّع:
- لا بد من كتابة سطر من لغة الآلة لكل تعليمة في الآلة، وبالتالي تتطلب البرامج الكبيرة آلافاً من الأسطر المكتوبة بلغة المجمّع؛
- يطلب من المبرمجين معرفة الخصائص الوظيفية الداخلية للحاسوب؛
- يتوجب ترجمة البرنامج المكتوب بلغة التجميع باستخدام برنامج خاص يدعى المجمع (Assembler)، وذلك قبيل التنفيذ.
compiler
الCompiler هو اسم الmodule الذي يقبل input من لغة High Level مثل الC و يعطي output من الObject Code المناظر له..
يتميز المترجم عن المجمّع بحقيقة وحيدة، وهي أن كل تعليمة من تعليمات الدخل لا تكافئ تعليمة واحدة من تعليمات لغة الآلة، أو سلسلة متكاملة من التعليمات. هذا، ويمكن للمترجم دعم عدة ميزات، كالتحديد التلقائي لمواضع المتحولات، والتعابير الحسابية، وبنى التحكم، كحلقات FOR و WHILE، ومجالات رؤية المتحولات، وعمليات الدخل والخرج، والتوابع ذات الترتيب الأعلى ومحمولية التشفير المصدري
ويتم الإعلان عن الخطأ إذا تم اكتشافه أثناء الترجمة، أما إذا كانت الأخطاء خطيرة، فقد يتوقف المترجم عن تتمة عملية الترجمة كلياً. إضافة، ينبغي على المترجم أن يتمكن من الدخول إلى مكتبة الإجرائيات أو البرامج الجزئية، حيث تتضمن هذه المكتبة العديد من البرامج والإجرائيات اللازمة لتنفيذ العمليات الحسابية، وعمليات الدخل والخرج، والتخزين الوقائي لنقاط تحويل المعطيات. وحيثما أشار البرنامج المصدري لإحدى هذه الإجرائيات، أو احتاج لتنفيذ عملية محددة، يقوم المترجم بالتأكد من إضافة الإجرائية المكتوبة بلغة الآلة إلى التشفير العنصر
وفي حالة تم تعديل البرنامج المصدري لاحقاً، فينبغي إعادة كامل عملية الترجمة وهذا الامر مرهق احيانا
Interpreter
الinterpreter هو اسم الmodule الذي يقوم بعملية الترجمة للسطر ثم ينتظر تنفيذه ثم ينتقل للسطر الأخر و هكذا و هو يشبه الى حد ما الCompiler و لكنه في أغلب الأحيان لا يمكن ان تُنتج منه Executable File أي أنه يجب أن يقوم الinterpreter بعمل الترجمة كل مرة لتشغيل البرنامج كما أن مهمته تزداد تعقيداً في وجود الLoops و الConditions و لأنه يجب عليه أن يترجم الloop كلها ثم يحتفظ بالكود الخاص بها في الذاكرة حتى يكررها ثم ينتقل بعد هذا الى مابعدها...
وبدون الخروج عن الهدف الاصلى من الدرس وهو ان اوضح احد افكار تصميم الاسمبلر (Assembler)
واحدة من الوسائل التى تستخدم فى صناعة الاسمبلر هى كتابة الSyntax للغة الاسمبلى ( assembly language) بالBNF ومن ثم زيادته بالحاق كل قاعدة باسم الدالة (function ) ليتم منادتها عندما تتفق القاعدة
وعلى سبيل المثال سوف نطلق على على زيادة الSyntax اسم +BNF
عندما نمتلك محرك BNF الاساسى يعمل فاننا نستطيع بناء الاسمبلر له وفى العادة تسمى ( Compiler-Compiler) >>>(لكن هذا اسمبلر وليس مترجم )
نستطيع كتابة BNF لنفسه وفى مثالنا +BNF نقوم بادخاله على اساس انه Input ألى الEngine
وتخرج مجموعة من جداول التحكم (control tables) سيكون المحرك على هذا النمط وهو سوف يكون قادرا على قراءة اى شئ فى +BNF (على سبيل المثال لغة الاسمبلى المعروفة)
بالاضافة الى محرك BNF فنحن نحتاج الى symbol table وايضا دوال back-end code generation
تستطيع عمل كل هذا بدون tokeniser بتعريف Token على BNF , والسماح للمحرك الرئيسى بعمل هذا العمل وهذا سهل لكنه يعيبه البطئ
وهذه بعض الامثلة غير الكاملة للتوضيح لكنها ينبغى ان يقوم بتوضيح الامور التى اريد ايصالها
قطعة من BNF + ملف بناء Syntax :-
* rule elements semantic function
file = {oneline} [eof] .
oneline = line ending . NextLine
*-----------------------------------------------
* Basic elements
eof = #26 .
eol = {ws} [comment] #10 .
ws = wsc {wsc} .
wsc = " " |
#9 .
digit = ("0".."9") .
uphex = ("A".."F") .
lowhex = ("a".."f") .
upper = ("A".."Z") .
lower = ("a".."z") .
hexchar = digit | HexValueDigit
uphex | HexValueUpper
lowhex . HexValueLower
letter = upper |
lower .
symch = letter |
digit |
"_" .
Top-level function of the BNF+ engine:
// Syntax "engine": the core of the system
#define SP current.syntab[current.synptr]
#define GETCHR {currch = getchr(¤t);}
#define NEXTCH {lastch = currch; current.fileptr++; \
GETCHR; if(currch=='\n') current.linum++;}
#define GOTOCH(x) {current.fileptr = x; GETCHR}
#define EXIT {if(init.filename) fclose(init.input); \
return(current);}
state engine(state init)
{
state current; // Local copy (we may want to back-up)
state temp; // Workspace for recursive calls
WORD rule;
char error[100];
char currch;
init.depth++;
if(init.filename) // Open new file...
{
if(!(init.input = fopen(init.filename, "rb")))
{
sprintf(error,"Fatal: cannot open file %s\n", init.filename);
fatal(error);
}
init.fileptr = 0; // Start at BOF
init.linum = 1;
getchr(NULL); // Force a re-read
}
current = init;
current.filename = NULL; // File dealt with
GETCHR;
do
{
rule = SP;
currstate = ¤t; // Expose it for logging
#if LOGSTATE
{
static int ctr = 0;
char zz[8];
if((currch >= 0x20) && (currch <= 0x7e))
sprintf(zz,"<%c> ",currch);
else
sprintf(zz,"0x%02x", currch);
fprintf(logfile,"%5d %3d%6d [%4d] %s [%4d] %c\n",ctr++,
init.depth, rule,current.synptr, zz, current.fileptr, (current.match) ?
'T' : 'F');
fflush(logfile);
}
#endif
if(rule < 0) // SUB RULE
{
if(current.match)
{
temp = current;
temp.synptr = -rule; // Rule address
temp = engine(temp);
current.match &= temp.match;
if(temp.match)
GOTOCH(temp.fileptr);
}
}
else
{
if(rule < 200) // LITERAL CHAR
{
current.match &= ((char)rule == currch);
if(current.match)
NEXTCH
}
else
switch(rule)
{
case 202: // '|' ALTERNATE RULE
if(!current.match)
{ // Last test failed:
back-up & try next
current.match = init.match;
current.fileptr = init.fileptr;
current.synptr++; // To start of new rule
break;
}
// Else, fall through (rule succeeded)
case 201: // '.' END OF RULE
current.synptr++; // Point at semantic
if(current.match)
{
#if LOGSEMANTIC
char zz[8];
if((lastch >= 0x20) && (lastch <= 0x7e))
sprintf(zz,"<%c> ",lastch);
else
sprintf(zz,"0x%02x", lastch);
if(SP)
fprintf(logfile," Semantic[%4d] %s\n",SP,zz);
fflush(logfile);
#endif
semantics[SP](); // Semantic function (zero
is valid)
}
EXIT;
case 203: // '{'
case 204: // '['
temp = current;
temp.synptr++;
temp = engine(temp);
if(temp.match) // If it worked, swallow
the chars.
GOTOCH(temp.fileptr);
if(current.syntab[temp.synptr] != 2+SP)
{
sprintf(error,"Fatal: bracket imbalance at
%d:%d\n", temp.synptr, current.synptr);
fatal(error);
}
if(temp.match && (current.syntab[temp.synptr] == 205))
continue; // {...} repeats indefinitely
else
current.synptr = temp.synptr;
break;
case 205: // '}'
case 206: // ']'
EXIT;
case 207: // RANGE
current.match &= ((currch >=
(char)current.syntab[current.synptr+1]) && (currch <=
(char)current.syntab[current.synptr+2]));
current.synptr += 2;
if(current.match)
NEXTCH;
break;
case 208: // '@' - include file
temp = current;
temp.filename = Identifier;
temp.fileptr = 0; // Start at BOF of inner file
current.synptr++; // Point to nested rule
temp.synptr = -SP;
temp = engine(temp); // Run the nested file
current.match &= temp.match;
default:
{
sprintf(error,"Fatal: unrecognised syntax code at
[%d] = %d\n", current.synptr, rule);
fatal(error);
}
}
}
if(!current.match) // If this rule failed, back-up
GOTOCH(init.fileptr)
do
{
current.synptr++; // Next syntax item (skip if tests
have failed)
} while(!(current.match || (SP > 200)));
} while(1); // End the DO loop
}الدرس بسيط والفكرة ابسط وان شاء الله سأحاول الا يكون الدرس الوحيد وسوف اتبعه بمقالات جديدة عندما اجد الوقت
طبعا فكرة الدرس بسيطة جدا وهذا المجال صناعة الاسمبلرات كبير جدا ومتشعب ويحتاج دراسة
وانا انصح لمن يريد الاستزادة بكتاب Art Of Computer Programming
طبعا انا منتظر اى نقد او اقتراحات او اخطاء تصحيحها من الخبراء والاخوة الافاضل ومنكم نستفيد
وتحياتى للجميع