
مقدمة
تحياتي لجميع الذين سقطوا لقراءة مقالتي القادمة.
أكرر ، أصف إنشاء لغة لغة برمجة استنادًا إلى العمل السابق ، وقد تم
وصف نتائجها
في هذا المنشور .
في الجزء الأول (link:
habr.com/post/435202 ) وصفت مراحل تصميم وكتابة لغة VM ستنفذ تطبيقاتنا المستقبلية
بلغتنا المستقبلية.
في هذه المقالة ، أخطط لوصف المراحل الرئيسية لإنشاء لغة برمجة وسيطة سيتم تجميعها في رمز مجردة للتنفيذ المباشر على VM الخاص بنا.
أعتقد أنه لن يضر بتوفير روابط مباشرة إلى موقع المشروع ومخزنه.
الموقعمستودعيجب أن أقول على الفور أن جميع الشفرة مكتوبة في FPC وسأقدم أمثلة عليها.
لذلك ، نبدأ التنوير لدينا.
لماذا استسلمنا للغة الوسيطة؟
يجدر بنا أن نفهم أن تحويل البرنامج من لغة عالية المستوى على الفور إلى رمز تنفيذي قابل للتنفيذ ، والذي يتكون من مجموعة محدودة من التعليمات ، أمر تافه لدرجة أنه من الأفضل تبسيطه بترتيب كبير من خلال إضافة لغة وسيطة إلى المشروع. من الأفضل بكثير تبسيط الكود تدريجيًا بدلاً من تقديم التعبيرات الرياضية والهياكل والفئات الرياضية على الفور بمجموعة من الأكواد البرمجية. بالمناسبة ، هذه هي الطريقة التي يعمل بها معظم المترجمين التحريريين والمترجمين.
في مقالي السابق ، كتبت عن كيفية تطبيق لغة VM. نحن الآن بحاجة إلى تطبيق لغة تشبه المجمّع لها ووظائفها لمزيد من كتابة المترجم. في هذه المراحل ، نضع الأساس لمشروع المستقبل. يجدر بنا أن نفهم أنه كلما كان الأساس أفضل ، كان المبنى أكثر انحدارًا.
نأخذ الخطوة الأولى لتحقيق هذه المعجزة
لبداية ، الأمر يستحق تحديد هدف. ما سوف نكتب فعلا؟ ما هي الخصائص التي يجب أن يكون الرمز النهائي وماذا يجب أن تفعل؟
يمكنني إنشاء قائمة بالأجزاء الوظيفية الرئيسية التي يجب أن يتكون منها هذا الجزء من المشروع:
- مجمع بسيط. يحول التعليمات البسيطة إلى مجموعة من رموز شفرة VMs.
- التنفيذ الأساسي للوظيفة لتنفيذ المتغيرات.
- التنفيذ الأساسي للوظيفة للعمل مع الثوابت.
- وظيفة دعم نقاط الدخول للطرق وحساب عناوينها في مرحلة الترجمة.
- ربما زوجين الكعك أكثر وظيفية.
يوضح الرسم التوضيحي أعلاه جزءًا من الكود بلغة وسيطة يتم تحويله إلى كود لـ VM بواسطة مترجم بدائي ، والذي سيتم مناقشته.
لذلك ، يتم تحديد الأهداف ، دعنا ننتقل إلى التنفيذ.
كتابة مجمع بسيط
نسأل أنفسنا ما هو المجمع؟
في الواقع ، هذا هو البرنامج الذي يقوم بإجراء استبدال رموز التشغيل بدلاً من أوصافها النصية.
النظر في هذا الرمز:
push 0 push 1 add peek 2 pop
بعد معالجة رمز المجمّع ، نحصل على الكود القابل للتنفيذ لـ VM.
نرى أن التعليمات يمكن أن تكون أحادية الاتجاه ومزدوجة الشكل. لا توجد تعليمات أكثر تعقيدًا لـ VM مكدسة.
نحتاج إلى رمز يمكنه استخراج الرموز المميزة من سلسلة (نأخذ في الاعتبار أنه قد توجد سلاسل بينها).
نكتبها:
function Tk(s: string; w: word): string; begin Result := ''; while (length(s) > 0) and (w > 0) do begin if s[1] = '"' then begin Delete(s, 1, 1); Result := copy(s, 1, pos('"', s) - 1); Delete(s, 1, pos('"', s)); s := trim(s); end else if Pos(' ', s) > 0 then begin Result := copy(s, 1, pos(' ', s) - 1); Delete(s, 1, pos(' ', s)); s := trim(s); end else begin Result := s; s := ''; end; Dec(w); end; end;
حسنًا ، نحتاج الآن إلى تنفيذ شيء ما مثل بنية حالة التبديل لكل عبارة ، والمجمع البسيط لدينا جاهز.
المتغيرات
أذكر أن VM لدينا لديه مجموعة من المؤشرات لدعم المتغيرات ، وبالتالي ، عنونة ثابتة. هذا يعني أنه يمكن تمثيل الوظيفية للعمل مع المتغيرات كقائمة TStringList ، حيث تكون السلاسل أسماء المتغيرات وتكون مؤشراتها هي عناوينها الثابتة. يجب أن يكون مفهوما أن ازدواجية أسماء المتغيرات في هذه القائمة أمر غير مقبول. أعتقد أنه يمكنك تخيل الرمز اللازم و / أو حتى كتابته بنفسك.
إذا كنت تريد إلقاء نظرة على التنفيذ النهائي ، فأنت مرحب بك: /lang/u_variables.pas
الثوابت
المبدأ هنا هو نفسه كما هو الحال مع المتغيرات ، ولكن هناك شيء واحد. من أجل التحسين ، من الأفضل عدم ربط أسماء الثوابت بل بقيمها. أي يمكن أن تحتوي كل قيمة ثابتة على قائمة TStringList ، والتي ستعمل على تخزين أسماء الثوابت بهذه القيمة.
بالنسبة للثوابت ، يجب عليك تحديد نوع البيانات ، وبالتالي ، من أجل إضافتها إلى اللغة ، يجب عليك كتابة محلل صغير.
التنفيذ: /lang/u_consts.pas
طريقة إدخال النقاط
لتنفيذ حظر الكود ، دعم التصاميم المختلفة ، إلخ. يجب تنفيذ دعم هذه الوظيفة على مستوى المجمّع.
النظر في مثال التعليمات البرمجية:
Summ: peek 0 pop peek 1 pop push 0 new peek 2 mov push 2 push 0 add jr
ما سبق هو مثال لترجمة طريقة Summ:
func Summ(a, b): return a + b end
يجب أن يكون مفهوما أنه لا توجد رموز تشغيل لنقاط الدخول. ما هي نقطة الدخول إلى طريقة Summ؟ هذا الرقم الأولي هو إزاحة نقطة إدخال شفرة التشغيل التالية. (إزاحة شفرة التشغيل هي رقم شفرة التشغيل بالنسبة لبداية رمز الملخص القابل للتنفيذ). الآن لدينا مهمة - نحتاج إلى حساب هذا الإزاحة في مرحلة التحويل البرمجي ، وكخيار ، نعلن أن ثابت Summ هو هذا الرقم.
نكتب لهذا عداد وزن معين لكل مشغل. لدينا مشغلات أحادية الاتجاه بسيطة ، على سبيل المثال "pop". يشغلونها 1 بايت. هناك أكثر تعقيدًا منها ، على سبيل المثال ، "دفع 123" - يشغلونها 5 بايت وواحد للكود التشغيل و 4 للكتابة غير الموقعة.
جوهر الكود لإضافة دعم لمجمع نقاط الدخول:
- لدينا عداد ، دعنا نقول i = 0.
- يتم تشغيلنا من خلال الكود ، إذا كان لدينا إنشاء من النوع "دفع 123" ، ثم أضف 5 إليه ، إذا كان كود التشغيل البسيط هو 1. إذا كان لدينا نقطة دخول ، فقم بإزالته من الكود ونعلن الثابت المقابل مع قيمة العداد واسم نقطة الدخول.
وظائف أخرى
هذا ، على سبيل المثال ، هو تحويل رمز بسيط قبل المعالجة.
ملخص
لقد قمنا بتطبيق المجمّع الصغير الخاص بنا. سنحتاج إليها لتنفيذ مترجم أكثر تعقيدًا بناءً عليه. الآن يمكننا كتابة برامج صغيرة لدينا VM. وفقا لذلك ، في مقالات أخرى سيتم وصف عملية كتابة مترجم أكثر تعقيدا.
شكرا لك على القراءة حتى النهاية إذا فعلت.
إذا كان هناك شيء غير واضح لك ، فأنا في انتظار تعليقاتكم.