نكتب لغة البرمجة لدينا ، الجزء 3: هندسة المترجم. تحليل هياكل اللغة والتعبيرات الرياضية

الصورة

مقدمة


أرحب بكم ، مطوري القراءة المهتمين بغض النظر عن اللغات التي أوجه بها هذه المقالات وأقدر دعمهم وآرائهم.

بالنسبة للمبتدئين ، وفقًا للتقاليد المعمول بها ، سأوفر روابط لمقالات سابقة:

الجزء 1: كتابة لغة VM
الجزء 2: عرض متوسط ​​للبرامج

لتكوين فهم كامل لما نكتبه في هذه المقالات ، يجب أن تتعرف على الأجزاء السابقة مقدمًا.

وأيضًا ، يجب أن أنشر فورًا رابطًا لمقال حول مشروع كتبت في وقت سابق واستند إلى هذا الاستخلاص كله: Clack syudy ربما يجدر بنا التعرف على الأمر أولاً.

والقليل عن المشروع:

موقع مشروع صغير
جيثب مستودع

حسنًا ، سأقول أيضًا على الفور أن كل شيء مكتوب في كائن Pascal ، وبالتحديد في FPC.

لذلك دعونا نبدأ.

مبدأ تشغيل معظم المترجمين


بادئ ذي بدء ، يجدر بنا أن نفهم أنني لا أستطيع أن أكتب أي شيء مفيد دون أن أتعرف أولاً على مجموعة من المواد النظرية وعدد من التماثيل. سأصف الشيء الرئيسي في بضع كلمات.

تتمثل مهمة المترجم أولاً وقبل كل شيء في تحضير الكود للتحليل (على سبيل المثال ، لإزالة التعليقات منه) وتقسيم الكود إلى رموز (الرمز المميز هو مجموعة الأحرف الدنيا ذات معنى للغة).

بعد ذلك ، من خلال التحليل والتحويل ، تحتاج إلى تحليل الكود إلى تمثيل وسيط معيّن ثم تجميع التطبيق جاهز للتنفيذ أو ... ماذا يحتاج إلى التجميع بشكل عام.

نعم ، لم أقل شيئًا في هذه المجموعة من النص ، ولكن المهمة الآن مقسمة إلى عدة مهام فرعية.

دعنا نتخطى كيفية إعداد الكود للتنفيذ ، لأن من الممل جدًا وصف العملية. افترض أن لدينا مجموعة من الرموز جاهزة للتحليل.

تحليل الكود


ربما تكون قد سمعت عن بناء شجرة الكود وتحليلها ، أو حتى المزيد من الأشياء الفظيعة. كما هو الحال دائمًا - هذا ليس أكثر من تشوش المصطلحات الرهيبة البسيطة. عن طريق تحليل الكود ، أعني مجموعة أبسط من الإجراءات. وتتمثل المهمة في تجاوز قائمة الرموز وتحليل الرمز ، كل من بنائه.

وكقاعدة عامة ، بلغات الشفرة ، يتم تقديم الكود بالفعل في شكل نوع من الأشجار من الهياكل.

يجب أن تعترف أنه من غير المقبول أن تبدأ الدورة "A" في جسم الدورة "B" ، وأنهيها خارج جسم الدورة "B". الكود عبارة عن هيكل يتكون من مجموعة من التركيبات.
وماذا لدى كل تصميم؟ هذا صحيح - البداية والنهاية (وربما شيء آخر في الوسط ، ولكن ليس الهدف).

وفقًا لذلك ، يمكن إجراء تحليل الكود مرة واحدة ، حقًا دون بناء شجرة.
للقيام بذلك ، تحتاج إلى حلقة ستعمل من خلال الكود وحالة تبديل ضخمة ستقوم بتحليل وتحليل الكود الرئيسي.

أي نركض عبر الرموز المميزة ، ولدينا رمز مميز (على سبيل المثال ، فليكن ...) "if" - أنا حقًا أشك في أن مثل هذا الرمز يمكن أن يكون في الكود تمامًا مثل ذلك -> هذه هي بداية الإنشاء إذا ... ثم [.. else] .. النهاية!

نقوم بتحليل جميع الرموز اللاحقة ، على التوالي ، لبناء الظروف في لغتنا.

قليلا عن الأخطاء رمز


في مرحلة تحليل الهياكل وتشغيلها على طول الكود ، من الأفضل ألا تسجل معالجة الأخطاء. هذه وظيفة مترجم مفيدة. إذا حدث خطأ أثناء تحليل الهيكل ، فسيكون ذلك منطقيًا - لم يتم بناء الهيكل بشكل صحيح ويجب عليك إبلاغ المطور.

الآن عن الهريس. كيف يتم تحليل لغة اللغة؟


أعلاه ، وصفت مفهومًا عامًا لجهاز الترجمة. لقد حان الوقت للحديث عن عملي.

في الواقع ، تبين أن المترجم يشبه إلى حد كبير المترجم أعلاه. لكن بالنسبة لي لا يقسم الرمز إلى مجموعة من الرموز لمزيد من التحليل.

قبل بدء التحليل ، يتم تقديم الرمز في شكل أكثر جمالا. يتم حذف التعليقات ويتم دمج كل الإنشاءات في طوابير طويلة إذا تم وصفها في عدة أسطر.

وبالتالي ، في كل سطر على حدة اتخذت بناء اللغة أو جزء منه. هذا رائع ، والآن يمكننا تحليل كل سطر في صندوق التبديل الكبير لدينا ، بدلاً من البحث عن هذه التركيبات في المجموعة المميزة. أيضا ، الميزة هنا هي أن الخط لديه نهاية وأنه من الأسهل تحديد الأخطاء في البناء مع هذا النهج.

وفقًا لذلك ، يحدث تحليل الهياكل الفردية بطرق منفصلة ، والتي تُرجع تمثيلًا متوسطًا لرمز الهياكل أو أجزائه.

ص. في مقال سابق ، وصفت بناء مترجم من لغة متوسطة إلى رمز ثانوي لـ VM. في الواقع - هذه اللغة الوسيطة هي تمثيل وسيط.

يجب أن يكون مفهوما أن الهياكل قد تتكون من عدة أبسط الهياكل. بسبب نظرًا لأننا نحلل كل هيكل بطرق منفصلة ، يمكننا بسهولة الاتصال بهم من بعضهم البعض عند تحليل كل هيكل.

الصورة

الاحماء المدى على رمز


بادئ ذي بدء ، يجب أن يتعرف المترجم على الشفرة بسرعة ، ويذهب عليها وينتبه لبعض التصميمات.

في هذه المرحلة ، يمكنك التعامل مع المتغيرات العالمية ، واستخدامات الإنشاءات ، بالإضافة إلى عمليات الاستيراد والإجراءات والوظائف ، وبناءات OOP.

من الأفضل إنشاء طريقة عرض وسيطة في العديد من الكائنات للتخزين ، بحيث
الصق رمز المتغيرات العامة بعد التهيئة ، ولكن قبل بدء main ().

يمكن إدراج رمز بنيات OOP في النهاية.

تصاميم متطورة


حسنا ، لقد اكتشفنا التصاميم البسيطة. الآن حان الوقت لصعبة. لا أعتقد أنك تمكنت من نسيان المثال بدورتين. كما نعلم ، عادة ما تأتي الهياكل على شكل شجرة. هذا يعني أنه يمكننا تحليل الهياكل المعقدة باستخدام المكدس.

ما علاقة المكدس به؟ علاوة على ذلك

أولاً ، نحن نصف الفصل الذي سنضغط عليه. عند تحليل الإنشاءات المعقدة ، يمكننا توليد تمثيل وسيط لبداية ونهاية هذه الكتلة ، على سبيل المثال ، نقوم بتحليل for ، بينما ، حتى loop ، إذا تم إنشاء طرق وأساليب وكل شيء في لغة Mash بالفعل.

يحتاج هذا الفصل إلى حقول لتخزين العروض الوسيطة ، ومعلومات التعريف (لبعض الإنشاءات المتغيرة) ، وبالطبع لتخزين نوع الكتلة.

سأقدم الرمز بالكامل ، لأنه لا يوجد الكثير منه:

unit u_prep_codeblock; {$mode objfpc}{$H+} interface uses Classes, SysUtils; type TBlockEntryType = (btProc, btFunc, btIf, btFor, btWhile, btUntil, btTry, btClass, btSwitch, btCase); TCodeBlock = class(TObject) public bType: TBlockEntryType; mName, bMeta, bMCode, bEndCode: string; constructor Create(bt: TBlockEntryType; MT, MC, EC: string); end; implementation constructor TCodeBlock.Create(bt: TBlockEntryType; MT, MC, EC: string); begin inherited Create; bType := bt; bMeta := MT; bMCode := MC; bEndCode := EC; end; end. 

حسنًا ، المكدس هو TList البسيط ، وإعادة اختراع العجلة هنا مجرد غباء عادي.

وبالتالي ، تحليل البناء ، دعنا نقول الشيء نفسه بينما تبدو الحلقة كما يلي:

 function ParseWhile(s: string; varmgr: TVarManager): string; var WhileNum, ExprCode: string; begin Delete(s, 1, 5); //"while" Delete(s, Length(s), 1); //":" s := Trim(s); //   ,       ... //        :) WhileNum := '__gen_while_' + IntToStr(WhileBlCounter); Inc(WhileBlCounter); //   while   // ,        if IsExpr(s) then ExprCode := PreprocessExpression(s, varmgr) else ExprCode := PushIt(s, varmgr); //  ExprCode     //        //    //(      ) Result := WhileNum + ':' + sLineBreak + 'pushcp ' + WhileNum + '_end' + sLineBreak + ExprCode + sLineBreak + 'jz' + sLineBreak + 'pop'; //        //  -        //   break BlockStack.Add(TCodeBlock.Create(btWhile, '', 'pushcp ' + WhileNum + sLineBreak + 'jp' + sLineBreak + WhileNum + '_end:', WhileNum + '_end')); end; 

حول تعبيرات الرياضيات


ربما لم تلاحظ هذا ، لكن التعبيرات الرياضية / المنطقية هي أيضًا رمز منظم.

لقد نفذت تحليلهم بطريقة مكدسة. أولاً ، يتم دفع جميع العناصر الفردية للتعبير إلى المكدس ، ثم يتم إنشاء رمز التمثيل الوسيط في عدة تمريرات.

عدة مرات - لأن هناك عمليات رياضية ذات أولوية ، مثل الضرب.
لا أرى النقطة هنا ، لأن إنه كثير منه وهو ممل.

ص. /lang/u_prep_expressions.pas - هنا يتم كشفها بشكل كامل وكامل لمراجعتك.

ملخص


لذلك ، قمنا بتنفيذ مترجم يمكنه التحويل ... على سبيل المثال ، هذا هو الكود:

 proc PrintArr(arr): for(i ?= 0; i < len(arr); i++): PrintLn("arr[", i, "] = ", arr[i]) end end proc main(): var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] PrintArr(arr) InputLn() end 

ما هو نقص في لغتنا؟ الحق ، ودعم OOP. سنتحدث عن هذا في مقالتي القادمة.

شكرا لك على القراءة حتى النهاية إذا فعلت.

إذا كان هناك شيء غير واضح لك ، فأنا في انتظار تعليقاتكم. أو أسئلة في المنتدى ، نعم ... نعم ، أتحقق منها في بعض الأحيان.

والآن استطلاع صغير (حتى أنظر إليه وأستمتع بأهمية مقالاتي):

Source: https://habr.com/ru/post/ar435520/


All Articles