نكتب لغة البرمجة لدينا ، الجزء 1: نكتب لغة VM

مقدمة


يوم جيد للجميع habrachitateli!

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

سوف أصف إنشاء اللغة التي وصفتها سابقًا هنا .

لقد اهتم بالكثيرين وأثار مناقشة ساخنة في التعليقات. لذلك - الموضوع مثير للكثيرين.

أعتقد أن الأمر يستحق نشر معلومات حول المشروع على الفور:

الموقع (سيتم ملؤه بالوثائق لاحقًا).
مستودع

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

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

لذلك ، ربما سأبدأ قصتي.

وضعنا الأهداف


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

فيما يلي النقاط الرئيسية التي حددت التطوير الإضافي لـ VM الخاص بي:

  • الكتابة الديناميكية ونوع الصب. قررت تنظيم دعمها في مرحلة تطوير جهاز VM.
  • دعم تعدد العمليات. قمت بتضمين هذا العنصر في هذه القائمة مقدمًا من أجل تصميم بنية VM بشكل صحيح وتنظيم دعم تعدد العمليات في المستوى الأساسي من VM ، وليس مع عكازات في وقت لاحق.
  • تصدير الطرق الخارجية. بدون هذا ، ستكون اللغة عديمة الفائدة. ما لم يتم تضمينه في أي مشروع.
  • تجميع للغة (في ملف تنفيذي منفرد مجرد). جمعت جزئيا أو فسرت؟ الكثير يعتمد على هذا.
  • العمارة VM العامة. هل سيكون المكدس أو التسجيل لدينا VM؟ حاولت تنفيذ هذا وذاك. اخترت VM مكدسة للحصول على الدعم.
  • كيف ترى العمل مع المتغيرات والمصفوفات والهياكل؟ أنا شخصياً ، في تلك اللحظة ، أردت تنفيذ لغة يرتبط بها كل شيء تقريبًا بمؤشرات ضمنية ، لأن مثل هذا النهج من شأنه أن ينقذ الذاكرة إلى حد كبير ويبسط حياة المطور. إذا سمحنا بتمرير شيء كبير إلى الأساليب ، فسيتم تلقائيًا نقل مؤشر إلى ذلك الحجم الكبير فقط.

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

يجب أن أقول على الفور أنني اتصلت بـ VM بأقصى درجة ممكنة - SVM (الجهاز الظاهري القائم على المكدس).

لنبدأ بتطبيق الفئة المتغيرة


في البداية ، استخدمت ببساطة نوعًا مختلفًا ، لأنه أبسط وأسرع. لقد كان عكازًا ، لكنه دعم المشروع وسمح لي بتنفيذ الإصدار الأول من VM واللغة بسرعة. بعد ذلك جلست في الكود وكتبت تطبيق "البديل" الخاص بي. في الجوهر ، تحتاج إلى كتابة فصل يخزن مؤشرًا إلى قيمة في الذاكرة ، في null/cardinal/int64/double/string/array تكون null/cardinal/int64/double/string/array . يمكن للمرء استخدام كتابة الحالات ، لكنني اعتقدت أنه سيكون من الأفضل تنفيذ الطريقة التي قمت بتنفيذها.

قبل البدء في كتابة شفرة الفصل الدراسي ، قررت أن أضع على الفور التوجيه {$ H +} في رأس الوحدة النمطية للحصول على دعم أكثر مرونة للسلاسل في اللغة المستقبلية.
ص. بالنسبة لأولئك الذين قد لا يدركون الفرق بين أوضاع H- و H + FPC.

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

 var s:string[256]; 
لذلك ، بالنسبة للمبتدئين ، سوف نعلن عن Enum نوعًا ، والذي سنستخدمه كعلم محدد لتحديد نوع البيانات حسب المؤشر:

 type TSVMType = (svmtNull, svmtWord, svmtInt, svmtReal, svmtStr, svmtArr); 

بعد ذلك ، نصف الهيكل الأساسي لنوعنا المتغير وبعض الطرق:

  TSVMMem = class m_val: pointer; m_type: TSVMType; constructor Create; destructor Destroy; procedure Clear; end; ... constructor TSVMMem.Create; begin m_val := nil; m_type := svmtNull; end; destructor TSVMMem.Destroy; begin Clear; end; procedure TSVMMem.Clear; inline; begin case m_type of svmtNull: { nop }; svmtWord: Dispose(PCardinal(m_val)); svmtInt: Dispose(PInt64(m_val)); svmtReal: Dispose(PDouble(m_val)); svmtStr: Dispose(PString(m_val)); svmtArr: begin SetLength(PMemArray(m_val)^, 0); Dispose(PMemArray(m_val)); end; else Error(reVarInvalidOp); end; end; 

لا يرث الفصل من أي شيء ، لذلك يمكن حذف المكالمات الموروثة في المنشئ والمدمّر. سأنتبه إلى التوجيه المضمن. من الأفضل إضافة {$ inline on} إلى رأس الملف ، بالتأكيد. استخدامه النشط في VMs زاد بشكل كبير من الإنتاجية (ميغابايت في مكان ما بنسبة 15-20 ٪!). وهي تخبر المترجم أن أفضل طريقة مضمنة في مكان الاحتجاج بها. سيكون رمز الإخراج أكبر قليلاً في النهاية ، ولكنه سيعمل بشكل أسرع. في هذه الحالة ، باستخدام المضمنة مستحسن.

حسنًا ، في هذه المرحلة ، غسلنا أساس فئتنا. الآن نحن بحاجة إلى وصف عدد من المستوطنين والعبارات (واضعة و getter) في فصلنا.

تتمثل المهمة في كتابة طريقتين تسمحان لك بالحصول على القيم من صفنا في وقت لاحق.

أولاً ، دعونا نتعرف على تعيين قيمة لفئتنا. أولاً ، يمكنك كتابة أداة ضبط معممة ، ومن ثم ، بالنسبة لأنواع البيانات الفردية:

 procedure TSVMMem.SetV(const value; t:TSVMType); inline; begin if (m_val <> nil) and (m_type = t) then begin case t of svmtWord: PCardinal(m_val)^ := Cardinal(value); svmtInt: PInt64(m_val)^ := Int64(value); svmtReal: PDouble(m_val)^ := Double(value); svmtStr: PString(m_val)^ := String(value); end; end else begin if m_val <> nil then FreeMem(m_val); m_type := t; case t of svmtWord: begin New(PCardinal(m_val)); PCardinal(m_val)^ := Cardinal(value); end; svmtInt: begin New(PInt64(m_val)); PInt64(m_val)^ := Int64(value); end; svmtReal: begin New(PDouble(m_val)); PDouble(m_val)^ := Double(value); end; svmtStr: begin New(PString(m_val)); PString(m_val)^ := String(value); end; else Error(reVarTypeCast); end; end; end; ... procedure TSVMMem.SetW(value:cardinal); inline; begin if (m_val <> nil) and (m_type = svmtWord) then PCardinal(m_val)^ := value else begin if m_val <> nil then FreeMem(m_val); m_type := svmtWord; New(PCardinal(m_val)); PCardinal(m_val)^ := value; end; end; 

الآن يمكنك كتابة رمز لبضع رسائل:

 function TSVMMem.GetW:cardinal; inline; begin Result := 0; case m_type of svmtWord: Result := PCardinal(m_val)^; svmtInt: Result := PInt64(m_val)^; svmtReal: Result := Trunc(PDouble(m_val)^); svmtStr: Result := StrToQWord(PString(m_val)^); else Error(reVarTypeCast); end; end; 

حسنًا ، لقد قضيت وقتًا طويلاً في التحديق في بيئة تطوير متكاملة (IDE) وكتابة رمز بحماس للمستوطنين والأفراد ، نواجه مهمة تنفيذ الدعم لنوع عملياتنا الرياضية والمنطقية. كمثال ، سأقدم تنفيذ عملية الإضافة:

 procedure TSVMMem.OpAdd(m:TSVMMem); inline; begin case m_type of svmtWord: case m.m_type of svmtWord: SetW(GetW + m.GetW); svmtInt: SetI(GetW + m.GetI); svmtReal: SetD(GetW + m.GetD); svmtStr: SetD(GetW + StrToFloat(m.GetS)); else Error(reVarInvalidOp); end; svmtInt: case m.m_type of svmtWord: SetI(GetI + m.GetW); svmtInt: SetI(GetI + m.GetI); svmtReal: SetD(GetI + m.GetD); svmtStr: SetD(GetI + StrToFloat(m.GetS)); else Error(reVarInvalidOp); end; svmtReal: case m.m_type of svmtWord: SetD(GetD + m.GetW); svmtInt: SetD(GetD + m.GetI); svmtReal: SetD(GetD + m.GetD); svmtStr: SetD(GetD + StrToFloat(m.GetS)); else Error(reVarInvalidOp); end; svmtStr: case m.m_type of svmtWord: SetS(GetS + IntToStr(m.GetW)); svmtInt: SetS(GetS + IntToStr(m.GetI)); svmtReal: SetS(GetS + FloatToStr(m.GetD)); svmtStr: SetS(GetS + m.GetS); else Error(reVarInvalidOp); end; else Error(reVarInvalidOp); end; end; 

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

 function TSVMMem.ArrGet(index: cardinal; grabber:PGrabber): pointer; inline; begin Result := nil; case m_type of svmtArr: Result := PMemArray(m_val)^[index]; svmtStr: begin Result := TSVMMem.CreateFW(Ord(PString(m_val)^[index])); grabber^.AddTask(Result); end; else Error(reInvalidOp); end; end; 

عظيم الآن يمكننا المضي قدما.

نحن ندرك كومة


بعد فترة من الوقت ، جئت إلى مثل هذه الأفكار. يجب أن تكون المكدس ثابتة (للسرعة) وديناميكية (للمرونة) في نفس الوقت.

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

أحمل تنفيذ المكدس بالكامل:

 type TStack = object public items: array of pointer; size, i_pos: cardinal; parent_vm: pointer; procedure init(vm: pointer); procedure push(p: pointer); function peek: pointer; procedure pop; function popv: pointer; procedure swp; procedure drop; end; PStack = ^TStack; procedure TStack.init(vm: pointer); begin SetLength(items, StackBlockSize); i_pos := 0; size := StackBlockSize; parent_vm := vm; end; procedure TStack.push(p: pointer); inline; begin items[i_pos] := p; inc(i_pos); if i_pos >= size then begin size := size + StackBlockSize; SetLength(items, size) end; end; function TStack.peek: pointer; inline; begin Result := items[i_pos - 1]; end; procedure TStack.pop; inline; begin dec(i_pos); if size - i_pos > StackBlockSize then begin size := size - StackBlockSize; SetLength(items, size); end; end; function TStack.popv: pointer; inline; begin dec(i_pos); Result := items[i_pos]; if size - i_pos > StackBlockSize then begin size := size - StackBlockSize; SetLength(items, size); end; end; procedure TStack.swp; inline; var p: pointer; begin p := items[i_pos - 2]; items[i_pos - 2] := items[i_pos - 1]; items[i_pos - 1] := p; end; procedure TStack.drop; inline; begin SetLength(items, StackBlockSize); size := StackBlockSize; i_pos := 0; end; 

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

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

نتحدث عن القمامة


عادة ما يكون كثيرًا ، كثيرًا. وتحتاج إلى القيام بشيء ما معها.

بادئ ذي بدء ، أريد أن أتحدث عن كيفية ترتيب جامعي القمامة بلغات أخرى ، على سبيل المثال ، في Lua و Ruby و Java و Perl و PHP ، إلخ. أنها تعمل على مبدأ عد المؤشرات إلى الأشياء في الذاكرة.

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

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

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

لذلك ، الآن سنتعامل مع التحويل البرمجي إلى ملف قابل للتنفيذ مجردة


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

للقيام بذلك ، تحتاج إلى تحديد تنسيق الملفات القابلة للتنفيذ. حصلت على ما يلي:

  1. الرأس ، على سبيل المثال "SVMEXE_CNS".
  2. قسم يحتوي على قائمة المكتبات التي سيتم استيراد الأساليب منها.
  3. يُشار إلى قسم الاستيراد بالطرق المطلوبة ، والمكتبات التي تُستورد منها الأساليب برقمها في القسم أعلاه.
  4. قسم الثوابت.
  5. قسم الكود

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

تنفيذ التعليمات البرمجية


بعد تحليل الأقسام أعلاه وتهيئة VM ، لدينا قسم واحد مع الكود. في جهاز VM الخاص بي ، يتم تنفيذ رمز فرعي غير محاذٍ ، أي يمكن أن تكون التعليمات ذات طول تعسفي.

مجموعة من أكواد التشغيل - تعليمات لجهاز ظاهري بتعليقات صغيرة أقدمها مقدمًا أدناه:

 type TComand = ( {** for stack **} bcPH, // [top] = [var] bcPK, // [var] = [top] bcPP, // pop bcSDP, // stkdrop bcSWP, // [top] <-> [top-1] {** jump's **} bcJP, // jump [top] bcJZ, // [top] == 0 ? jp [top-1] bcJN, // [top] <> 0 ? jp [top-1] bcJC, // jp [top] & push callback point as ip+1 bcJR, // jp to last callback point & rem last callback point {** for untyped's **} bcEQ, // [top] == [top-1] ? [top] = 1 : [top] = 0 bcBG, // [top] > [top-1] ? [top] = 1 : [top] = 0 bcBE, // [top] >= [top-1] ? [top] = 1 : [top] = 0 bcNOT, // [top] = ![top] bcAND, // [top] = [top] and [top-1] bcOR, // [top] = [top] or [top-1] bcXOR, // [top] = [top] xor [top-1] bcSHR, // [top] = [top] shr [top-1] bcSHL, // [top] = [top] shl [top-1] bcNEG, // [top] = -[top] bcINC, // [top]++ bcDEC, // [top]-- bcADD, // [top] = [top] + [top-1] bcSUB, // [top] = [top] - [top-1] bcMUL, // [top] = [top] * [top-1] bcDIV, // [top] = [top] / [top-1] bcMOD, // [top] = [top] % [top-1] bcIDIV, // [top] = [top] \ [top-1] bcMV, // [top]^ = [top-1]^ bcMVBP, // [top]^^ = [top-1]^ bcGVBP, // [top]^ = [top-1]^^ bcMVP, // [top]^ = [top-1] {** memory operation's **} bcMS, // memory map size = [top] bcNW, // [top] = @new bcMC, // copy [top] bcMD, // double [top] bcRM, // rem @[top] bcNA, // [top] = @new array[ [top] ] of pointer bcTF, // [top] = typeof( [top] ) bcSF, // [top] = sizeof( [top] ) {** array's **} bcAL, // length( [top] as array ) bcSL, // setlength( [top] as array, {stack} ) bcPA, // push ([top] as array)[top-1] bcSA, // peek [top-2] -> ([top] as array)[top-1] {** memory grabber **} bcGPM, // add pointer to TMem to grabber task-list bcGC, // run grabber {** constant's **} bcPHC, // push copy of const bcPHCP, // push pointer to original const {** external call's **} bcPHEXMP, // push pointer to external method bcINV, // call external method bcINVBP, // call external method by pointer [top] {** for thread's **} bcPHN, // push null bcCTHR, // [top] = thread(method = [top], arg = [top+1]):id bcSTHR, // suspendthread(id = [top]) bcRTHR, // resumethread(id = [top]) bcTTHR, // terminatethread(id = [top]) {** for try..catch..finally block's **} bcTR, // try @block_catch = [top], @block_end = [top+1] bcTRS, // success exit from try/catch block bcTRR, // raise exception, message = [top] {** for string's **} bcSTRD, // strdel bcCHORD, bcORDCH, {** [!] directly memory operations **} bcALLC, //alloc memory bcRALLC, //realloc memory bcDISP, //dispose memory bcGTB, //get byte bcSTB, //set byte bcCBP, //mem copy bcRWBP, //read word bcWWBP, //write word bcRIBP, //read int bcWIBP, //write int bcRFBP, //read float bcWFBP, //write float bcRSBP, //read string bcWSBP, //write string bcTHREXT,//stop code execution bcDBP //debug method call ); 

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

يتم تطبيق VM ككائن ، بحيث يمكنك بسهولة تنفيذ دعم multithreading.

أنه يحتوي على مؤشر إلى مجموعة مع رموز التشغيل ، IP (مؤشر التعليمات) - إزاحة التعليمات المنفذة ومؤشرات لهياكل VM الأخرى.

تنفيذ التعليمات البرمجية هي حالة تبديل كبيرة.

مجرد إعطاء وصف لل VM:

 type TSVM = object public ip, end_ip: TInstructionPointer; mainclasspath: string; mem: PMemory; stack: TStack; cbstack: TCallBackStack; bytes: PByteArr; grabber: TGrabber; consts: PConstSection; extern_methods: PImportSection; try_blocks: TTRBlocks; procedure Run; procedure RunThread; procedure LoadByteCodeFromFile(fn: string); procedure LoadByteCodeFromArray(b: TByteArr); end; 

قليلا عن معالجة الاستثناء


للقيام بذلك ، يحتوي VM على كومة من معالجات الاستثناءات وكتلة try / catch كبيرة يتم لف فيها تنفيذ التعليمات البرمجية. من المكدس ، يمكنك وضع بنية بها إزاحة نقطة دخول على الالتقاط وكتلة معالجة استثناء النهاية / النهاية. كما قدمت شفرة تشغيل trs ، والتي يتم وضعها قبل الالتقاط ورمي الكود إلى النهاية / النهاية إذا نجحت ، مع حذف الكتلة في نفس الوقت بمعلومات حول معالجات الاستثناءات من أعلى المكدس المقابل. هل هو بسيط؟ بسيط هل هي مريحة؟ مريح.

دعونا نتحدث عن الأساليب والمكتبات الخارجية


سبق أن ذكرت لهم من قبل. عمليات الاستيراد ، المكتبات ... بدونها ، لن تتمتع اللغة بالمرونة والوظائف المطلوبة.

بادئ ذي بدء ، في تطبيق VM ، نعلن نوع الأسلوب الخارجي وبروتوكول للاتصال به.

 type TExternalFunction = procedure(PStack: pointer); cdecl; PExternalFunction = ^TExternalFunction; 

عند استيراد VM ، يقوم محلل قسم الاستيراد بملء مجموعة من المؤشرات للطرق الخارجية. لذلك ، لكل طريقة عنوان ثابت ، يتم حسابه في مرحلة تجميع التطبيق تحت VM والتي يمكن من خلالها استدعاء الطريقة المطلوبة.

تحدث المكالمة لاحقًا بهذه الطريقة أثناء تنفيذ التعليمات البرمجية:

 TExternalFunction(self.extern_methods^.GetFunc(TSVMMem(self.stack.popv).GetW))(@self.stack); 

دعنا نكتب مكتبة بسيطة لدينا VM


ودعها تنفذ أولاً طريقة النوم:

 library bf; {$mode objfpc}{$H+} uses SysUtils, svm_api in '..\svm_api.pas'; procedure DSleep(Stack:PStack); cdecl; begin sleep(TSVMMem(Stack^.popv).GetW); end; exports DSleep name 'SLEEP'; end. 

ملخص


حول هذا سوف أقوم على الأرجح بإنهاء مقالي الأول من دورة متصورة.

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

يتوفر رمز VM الكامل في المستودع ، في فرع / runtime / svm.

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

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

ربما ستكون أسئلتك وإجاباتك مثيرة للاهتمام ليس لك فقط.

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


All Articles