إضافة إجراءات إلى قواعد PEG

القواعد النحوية أفضل إذا كنت تستطيع إضافة (بعض) دلالات وفقًا للقواعد. على وجه الخصوص ، بالنسبة إلى محلل Python الذي أقوم بتطويره ، أحتاج إلى إرجاع عقدة AST من كل بديل ، لأنني أرغب في الالتزام بتطبيق AST الحالي في CPython.



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


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


يكون بناء جملة الإجراءات هو التالي:


 rule: item item item { action 1 } | item item { action 2 } 

نظرًا لأن هذا يجعل القواعد أكثر مطوّلة ، عادةً ما تسمح المولدات اللغوية لقواعد متعددة الأسطر ، على سبيل المثال:


 rule: item item item { action 1 } | item item { action 2} 

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


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


في موزعي PEG مع عودتهم غير المحدودة إلى قائمة الرموز ، لدينا عدة خيارات:


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

لقد اخترت الخيار الثالث - على أي حال ، نقوم بتخزين ذاكرة التخزين المؤقت لنتائج الطريقة باستخدام خوارزمية packrat ، حتى نتمكن أيضًا من تخزين النتائج مؤقتًا.


بالنسبة للمحتوى الموجود في {curlies} ، فإنه يستخدم تقليديًا شفرة C مع اتفاقية تستند إلى $ للإشارة إلى عناصر في بديل معترف به (على سبيل المثال ، $1 للإشارة إلى العنصر الأول) والتخصيص $$ للإشارة إلى نتيجة الإجراء. هذا يبدو قديمًا جدًا (لدي ذكريات استخدام تعيين الوظيفة في Algol-60 للإشارة إلى قيمة الإرجاع) ، لذلك سأجعلها أكثر بيثونية: داخل الأقواس ستحتاج إلى وضع تعبير واحد ، وستكون النتيجة نتيجة الإجراء ، وستكون روابط العناصر أسماء بسيطة تعطي نص العنصر. على سبيل المثال ، فيما يلي آلة حاسبة بسيطة يمكنها إضافة وطرح الأرقام:


 start: expr NEWLINE { expr } expr: expr '+' term { expr + term } | expr '-' term { expr - term } | term { term } term: NUMBER { float(number.string) } 

لننفذه على سبيل المثال 100 + 50 - 38 - 70 . وقال انه سوف يحسب الجواب ، لأنه يتعرف على الأجزاء عن طريق حساب ((100 + 50) - 38) - 70 ، وهو بالطبع 42 .


تفصيل صغير واحد: في الإجراء الخاص TokenInfo ، يحتوي number المتغير على كائن TokenInfo ، لذلك تحتاج إلى استخدام السمة .string الخاصة به .string للحصول على الرمز المميز في شكل سلسلة.


ماذا نفعل عندما يكون لدى البديل تكرارات متعددة لها نفس اسم القاعدة؟ يعطي المولد اللغوي لكل حدث اسمًا فريدًا ، مضيفًا 1 ، 2 ، إلخ. لحوادث لاحقة داخل البديل نفسه. على سبيل المثال:


 factor: atom '**' atom { atom ** atom1 } | atom { atom } 

التنفيذ الكامل ممل إلى حد ما ، لذلك لا أريد أن أتناوله. أدعوك إلى النظر في مستودع التخزين الخاص بي واللعب باستخدام الكود :


 python3.8 -m story5.driver story5/calc.txt -g story5.calc.CalcParser 

التصور الآن يسمح لك بالتحرك جيئة وذهابا باستخدام مفاتيح الأسهم الأيمن والأيسر!


ترخيص لهذه المقالة ورمز استشهد: CC BY-NC-SA 4.0

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


All Articles