مكتبة PEG (تحليل قواعد التعبير) للرأس فقط في C++17. يمكنك البدء في استخدامه على الفور بمجرد تضمين peglib.h
في مشروعك.
نظرًا لأن هذه المكتبة تدعم فقط برامج التحويل البرمجي C++ 17، يرجى التأكد من تمكين خيار المحول البرمجي -std=c++17
. ( /std:c++17 /Zc:__cplusplus
لـ MSVC)
يمكنك أيضًا تجربة الإصدار عبر الإنترنت، PEG Playground على https://yhirose.github.io/cpp-peglib.
تم وصف بناء جملة PEG جيدًا في الصفحة 2 من المستند بواسطة Bryan Ford. يدعم cpp-peglib أيضًا بناء الجملة الإضافي التالي في الوقت الحالي:
'...'i
(عامل حرفي غير حساس لحالة الأحرف)
[...]i
(عامل فئة الأحرف غير حساس لحالة الأحرف)
[^...]
(عامل فئة الأحرف المرفوضة)
[^...]i
(عامل فئة الأحرف المرفوضة غير حساس لحالة الأحرف)
{2,5}
(عامل التكرار الذي يشبه Regex)
<
... >
(مشغل حدود الرمز المميز)
~
(تجاهل عامل التشغيل)
x20
(رقم سداسي عشري)
u10FFFF
(حرف Unicode)
%whitespace
(تخطي المسافة البيضاء تلقائيًا)
%word
(تعبير Word)
$name(
... )
(عامل التقاط النطاق)
$name<
... >
(عامل الالتقاط المسمى)
$name
(عامل الإسناد الخلفي)
|
(مشغل القاموس)
↑
(عامل القطع)
MACRO_NAME(
... )
(قاعدة ذات معلمات أو ماكرو)
{ precedence L - + L / * }
(تحليل تعبير infix)
%recovery(
... )
(عامل استرداد الأخطاء)
exp⇑label
أو exp^label
(تركيبة السكر لـ (exp / %recover(label))
)
label { error_message "..." }
(تعليمات رسالة الخطأ)
{ no_ast_opt }
(لا توجد تعليمات لتحسين عقدة AST)
سيتم إجراء فحص "نهاية الإدخال" كإعداد افتراضي. لتعطيل الشيك، يرجى الاتصال بـ disable_eoi_check
.
تدعم هذه المكتبة التحليل الخطي للوقت المعروف باسم تحليل Packrat .
ملاحظة مهمة لبعض توزيعات Linux مثل Ubuntu وCentOS: تحتاج إلى خيار -pthread
عند الارتباط. انظر أرقام 23 و46 و62.
أنا متأكد من أنك ستستمتع بهذا المقال الممتاز "التحليل العملي باستخدام PEG وcpp-peglib" بقلم بيرت هوبيرت!
هذه عينة حاسبة بسيطة ويوضح كيفية تعريف القواعد، وربط الإجراءات الدلالية بالقواعد، والتعامل مع القيم الدلالية.
// (1) قم بتضمين ملف الرأس#include <peglib.h>#include <assert.h>#include <iostream>using namespace peg;using namespace std;int main(void) { // (2) إنشاء محلل parser parser(R"( # قواعد للآلة الحاسبة... مضافة <- مضاعف '+' مضاف / مضاعف مضاعف <- ابتدائي '*' مضاعف / ابتدائي ابتدائي <- '(' مضاف ')' / رقم رقم <- < [ 0-9]+ > %مسافة بيضاء <- [ t]* )"); تأكيد(static_cast<bool>(parser) == true); // (3) إجراءات الإعداد parser["Additive"] = [](const SemanticValues &vs) {switch (vs.choice()) {case 0: // "Multiplicative '+' Additive" return Any_cast<int>(vs[0]) + Any_cast< int>(vs[1]);default: // "متعدد" يُرجع Any_cast<int>(vs[0]); } }; parser["Multiplicative"] = [](const SemanticValues &vs) {switch (vs.choice()) {case 0: // "Primary '*' Multiplicative" return Any_cast<int>(vs[0]) * Any_cast< int>(vs[1]);default: // "أساسي" يُرجع Any_cast<int>(vs[0]); } }; parser["Number"] = [](const SemanticValues &vs) {return vs.token_to_number<int>(); }; // (4) تحليل parser.enable_packrat_parsing(); // تمكين تحليل الحزمة. كثافة العمليات فال؛ parser.parse("(1 + 2) * 3 ", val); تأكيد(فال == 9); }
لإظهار أخطاء بناء الجملة في النص النحوي:
قواعد تلقائية = R"( # القواعد النحوية للآلة الحاسبة... المضاف <- المضاعف '+' المضاف / المضاعف المضاعف <- الابتدائي '*' المضاعف / الابتدائي الابتدائي <- '(' المضاف ')' / رقم الرقم <- < [ 0-9]+ > %مسافة بيضاء <- [ t]*)"; محلل محلل؛ parser.set_logger([](خط size_t, size_t col, const string& msg, const string &rule) { cerr << السطر << ": " << col << ": " << msg << "n"; });auto ok = parser.load_grammar(grammar);assert(ok);
هناك أربعة إجراءات دلالية متاحة:
[](القيم الدلالية الثابتة& vs، أي & dt) [](القيم الدلالية الثابتة& مقابل) [](القيم الدلالية& vs، أي&dt) [](القيم الدلالية ومقابلها)
تحتوي قيمة SemanticValues
على المعلومات التالية:
القيم الدلالية
معلومات السلسلة المطابقة
معلومات الرمز المميز إذا كانت القاعدة حرفية أو تستخدم عامل تشغيل حدود الرمز المميز
رقم الاختيار عندما تكون القاعدة هي "الاختيار ذو الأولوية"
any& dt
عبارة عن بيانات سياق "للقراءة والكتابة" والتي يمكن استخدامها لأي أغراض. يتم تعيين بيانات السياق الأولية بطريقة peg::parser::parse
.
يمكن أن يُرجع الإجراء الدلالي قيمة من نوع بيانات عشوائي، والتي سيتم تغليفها بواسطة peg::any
. إذا لم يُرجع المستخدم شيئًا في إجراء دلالي، فسيتم إرجاع القيمة الدلالية الأولى في الوسيطة const SemanticValues& vs
(المحلل اللغوي Yacc له نفس السلوك.)
هنا يظهر هيكل SemanticValues
:
بناء القيم الدلالية: محمي std::vector<any> { // إدخال النص مسار const char*؛ const char* ss; // سلسلة متطابقة std::string_view sv() const { return sv_; } // رقم السطر والعمود الذي توجد فيه السلسلة المطابقة std::pair<size_t, size_t> line_info() const; // الرموز الرموز المميزة::vector<std::string_view>؛ std::string_view token(size_t id = 0) const; // تحويل الرمز المميز std::string token_to_string(size_t id = 0) const; القالب <typename T> T token_to_number() const; // رقم الاختيار (0 فهرس) size_t Choice() const; // تحويل متجه القيمة الدلالية إلى متجه آخر قالب <typename T> ناقل<T> تحويل (size_t beg = 0, size_t end = -1) const; }
يستخدم المثال التالي عامل التشغيل <
... >
، وهو عامل تشغيل حدود الرمز المميز .
peg::parser parser(R"( ROOT <- _ TOKEN (',' _ TOKEN)* TOKEN <- < [a-z0-9]+ > _ _ <- [ trn]*)"); parser["TOKEN"] = [](const SemanticValues& vs) { // لا يتضمن "الرمز المميز" مسافات بيضاء زائدة الرمز التلقائي = vs.token(); };auto ret = parser.parse(" token1, token2 ");
يمكننا تجاهل القيم الدلالية غير الضرورية من القائمة باستخدام عامل التشغيل ~
.
peg::parser parser(R"( ROOT <- _ ITEM (',' _ ITEM _)* ITEM <- ([a-z0-9])+ ~_ <- [ t]*)"); parser["ROOT"] = [&](const SemanticValues& vs) { تأكيد(vs.size() == 2); // يجب أن يكون 2 بدلاً من 5.};auto ret = parser.parse(" item1, item2 ");
القواعد التالية هي نفس ما ورد أعلاه.
peg::parser parser(R"( ROOT <- ~_ ITEM (',' ~_ ITEM ~_)* ITEM <- ([a-z0-9])+ _ <- [ t]*)");
يتوفر دعم المسند الدلالي مع إجراء المسند .
peg::parser parser("NUMBER <- [0-9]+"); parser["NUMBER"] = [](const SemanticValues &vs) { return vs.token_to_number<long>(); }; parser["NUMBER"].predicate = [](const SemanticValues &vs,const std::any & /*dt*/, std::string &msg) { if (vs.token_to_number<long>() != 100) { msg = "خطأ في القيمة!!";إرجاع خطأ; } إرجاع صحيح؛ };long val;auto ret = parser.parse("100", val);assert(ret == true);assert(val == 100); ret = parser.parse("200"، val);assert(ret == false);
إجراءات الدخول والخروج متاحة أيضًا.
المحلل اللغوي ["RULE"].enter = [](const سياق &c, const char* s, size_t n, Any& dt) { std::cout << "أدخل" << std::endl; }; المحلل اللغوي ["RULE"] = [](const SemanticValues& vs, Any& dt) { std::cout << "الإجراء!" << ستد::endl; }; parser["RULE"].leave = [](const سياق &c, const char* s, size_t n, size_t matchlen, Any& value, Any& dt) { std::cout << "اترك" << std::endl; };
يمكنك تلقي معلومات الخطأ عبر المسجل:
parser.set_logger([](خط size_t, size_t col, const string& msg) { ... }); parser.set_logger([](خط size_t, size_t col, const string& msg, const string &rule) { ... });
كما ترون في المثال الأول، يمكننا تجاهل المسافات البيضاء بين الرموز المميزة تلقائيًا باستخدام قاعدة %whitespace
.
يمكن تطبيق قاعدة %whitespace
على الشروط الثلاثة التالية:
مسافات زائدة على الرموز
المسافات البادئة في النص
المسافات الزائدة على السلاسل الحرفية في القواعد
هذه رموز صالحة:
KEYWORD <- 'keyword' KEYWORDI <- 'case_insensitive_keyword' WORD <- < [a-zA-Z0-9] [a-zA-Z0-9-_]* > # token boundary operator is used. IDNET <- < IDENT_START_CHAR IDENT_CHAR* > # token boundary operator is used.
القواعد التالية تقبل one, "two three", four
.
ROOT <- ITEM (',' ITEM)* ITEM <- WORD / PHRASE WORD <- < [a-z]+ > PHRASE <- < '"' (!'"' .)* '"' > %whitespace <- [ trn]*
peg::parser parser(R"( ROOT <- 'hello' 'world' %whitespace <- [ trn]* %word <- [az]+)"); parser.parse("مرحبا بالعالم"); // OKparser.parse("helloworld"); // نانوغرام
peg::parser parser(R"( ROOT <- CONTENT CONTENT <- (ELEMENT / TEXT)* ELEMENT <- $(STAG CONTENT ETAG) STAG <- '<' $tag< TAG_NAME > '>' ETAG <- '< /' $tag '>' TAG_NAME <- 'b' / 'u' TEXT <- TEXT_DATA TEXT_DATA <- ![<] .)"); parser.parse("هذا <b>نص <u>اختبار</u></b>."); // OKparser.parse("هذا <b>اختبار <u></b> نص</u>."); // NGparser.parse("هذا <b>أ <u>نص اختباري</b>."); // نانوغرام
|
يسمح لنا عامل التشغيل بإنشاء قاموس كلمات للبحث السريع باستخدام بنية Trie داخليًا. لا داعي للقلق بشأن ترتيب الكلمات.
START <- 'This month is ' MONTH '.'
MONTH <- 'Jan' | 'January' | 'Feb' | 'February' | '...'
نحن قادرون على العثور على العنصر المطابق مع choice()
.
parser["MONTH"] = [](const SemanticValues &vs) { auto id = vs.choice(); };
وهو يدعم الوضع غير الحساس لحالة الأحرف.
START <- 'This month is ' MONTH '.'
MONTH <- 'Jan'i | 'January'i | 'Feb'i | 'February'i | '...'i
↑
يمكن للمشغل التخفيف من مشكلة أداء التراجع، ولكن لديه خطر تغيير معنى القواعد.
S <- '(' ↑ P ')' / '"' ↑ P '"' / P
P <- 'a' / 'b' / 'c'
عندما نحلل (z
بالقواعد المذكورة أعلاه، لا يتعين علينا التراجع في S
بعد مطابقة (
، لأنه تم إدراج عامل القطع هناك.
# Syntax
Start ← _ Expr
Expr ← Sum
Sum ← List(Product, SumOpe)
Product ← List(Value, ProOpe)
Value ← Number / T('(') Expr T(')')
# Token
SumOpe ← T('+' / '-')
ProOpe ← T('*' / '/')
Number ← T([0-9]+)
~_ ← [ trn]*
# Macro
List(I, D) ← I (D I)*
T(x) ← < x > _
فيما يتعلق بخوارزمية تسلق الأسبقية ، يرجى الاطلاع على هذه المقالة.
المحلل اللغوي (R"( EXPRESSION <- INFIX_EXPRESSION(ATOM, OPERATOR) ATOM <- NUMBER / '(' EXPRESSION ')' OPERATOR <- < [-+/*] > NUMBER <- < '-'؟ [0-9" ]+ > %whitespace <- [ t]* # إعلان ترتيب الأسبقية INFIX_EXPRESSION(A, O) <- A (يا أ)* { الأسبقية L + - L * / })"); parser["INFIX_EXPRESSION"] = [](const SemanticValues& vs) -> long { auto result = Any_cast<long>(vs[0]); if (vs.size() > 1) {auto ope = Any_cast<char>(vs[1]);auto num = Any_cast<long>(vs[2]);switch (ope) { case '+': النتيجة += رقم؛ استراحة؛ الحالة '-': النتيجة -= الأعداد؛ استراحة؛ الحالة '*': النتيجة *= الأعداد؛ استراحة؛ الحالة '/': النتيجة /= الأعداد؛ استراحة؛ } } نتيجة الإرجاع؛ }; parser["OPERATOR"] = [](const SemanticValues& vs) { return *vs.sv(); }; parser["NUMBER"] = [](const SemanticValues& vs) { return vs.token_to_number<long>(); };فال طويل; parser.parse(" -1 + (1 + 2) * 3 - -1"، val)؛assert(val == 9);
يمكن تطبيق تعليمات الأسبقية فقط على قاعدة نمط "القائمة" التالية.
Rule <- Atom (Operator Atom)* { precedence L - + L / * R ^ }
تحتوي تعليمات الأسبقية على إدخالات معلومات الأسبقية. يبدأ كل إدخال بالارتباط الذي يكون "L" (يسار) أو "R" (يمين)، ثم تتبعه الرموز المميزة للمشغل. الإدخال الأول لديه أعلى مستوى من الترتيب.
cpp-peglib قادر على إنشاء AST (شجرة بناء الجملة المجردة) عند التحليل. تعمل طريقة enable_ast
في فئة peg::parser
على تمكين الميزة.
ملاحظة: عقدة AST تحمل رمزًا مميزًا مطابقًا كـ std::string_vew
للأداء واستخدام أقل للذاكرة. تقع على عاتق المستخدمين مسؤولية الاحتفاظ بالنص المصدر الأصلي مع شجرة AST التي تم إنشاؤها.
peg::parser parser(R"( ... definition1 <- ... { no_ast_opt } definition2 <- ... { no_ast_opt } ... )"); parser.enable_ast(); shared_ptr<peg::Ast> ast; if (parser.parse("...", ast)) { cout << peg::ast_to_s(ast); ast = parser.optimize_ast(ast); cout << peg::ast_to_s(ast); }
يقوم optimize_ast
بإزالة العقد الزائدة لتسهيل عملية AST. إذا كنت تريد تعطيل هذا السلوك من قواعد معينة، فيمكن استخدام تعليمات no_ast_opt
.
فهو يستدعي peg::AstOptimizer
داخليًا للقيام بهذه المهمة. يمكنك إنشاء أدوات تحسين AST الخاصة بك لتناسب احتياجاتك.
راجع الاستخدامات الفعلية في مثال حاسبة AST ومثال لغة PL/0.
بدلاً من إنشاء محلل عن طريق تحليل نص بناء جملة PEG، يمكننا أيضًا إنشاء محلل يدويًا باستخدام مجموعات المحلل اللغوي . هنا مثال:
باستخدام ربط مساحة الاسم؛باستخدام مساحة الاسم std؛علامات المتجهات<string>؛ التعريف ROOT، TAG_NAME، _؛ ROOT <= seq(_, zom(seq(chr('['), TAG_NAME, chr(']'), _))); TAG_NAME <= oom(seq(npd(chr(']')), dot()))), [&](const SemanticValues& vs) { tag.push_back(vs.token_to_string()); }; _ <= zom(cls("t"));auto ret = ROOT.parse(" [tag1] [tag:2] [tag-3] ");
فيما يلي عوامل التشغيل المتوفرة:
المشغل | وصف | المشغل | وصف |
---|---|---|---|
تسلسل | تسلسل | تشو | الاختيار ذو الأولوية |
زوم | صفر أو أكثر | أوم | واحد أو أكثر |
اختيار | خياري | ايه بي دي | والمسند |
npd | ليس المسند | مضاءة | سلسلة حرفية |
liti | سلسلة حرفية غير حساسة لحالة الأحرف | cls | فئة الشخصية |
ncls | فئة الأحرف السلبية | مركز حقوق الإنسان | شخصية |
نقطة | أي شخصية | توك | حدود الرمز المميز |
ign | تجاهل القيمة الدلالية | CSC | التقاط النطاق |
كاب | يأسر | bkr | مرجع خلفي |
مدينة نيويورك | قاموس | قبل | تعبير إنفيكس |
تفصيل | تعبير إنفيكس | usr | محلل محدد من قبل المستخدم |
مندوب | تكرار |
من الممكن إضافة/تجاوز التعريفات.
بناء الجملة التلقائي = R"( ROOT <- _ 'Hello' _ NAME '!' _)"; قواعد extra_rules = { {"NAME"، usr([](const char* s, size_t n, SemanticValues& vs, Any& dt) -> size_t { static Vector<string>names = { "PEG", "BNF" }; for (const auto& name : الأسماء) {if (name.size() <= n && !name.compare(0, name.size(), s, name.size())) { return name.size(); طول} } إرجاع -1; // خطأ في التحليل )) }, {"~_"، زوم(cls("trn")) } };auto g = parser(syntax, extra_rules);assert(g.parse("Hello BNF! "));
يقبل cpp-peglib نص UTF8. .
يطابق نقطة ترميز Unicode. كمان يدعمك u????
.
يدعم cpp-peglib تقرير موضع خطأ الفشل الأبعد كما هو موضح في مستند Bryan Ford الأصلي.
للحصول على تقرير أفضل عن الأخطاء والاسترداد، يدعم cpp-peglib عامل التشغيل "الاسترداد" مع التسمية التي يمكن ربطها بتعبير الاسترداد ورسالة خطأ مخصصة. تأتي هذه الفكرة من الورقة الرائعة "Syntax Error Recovery in Parsing Expression Grammars" التي كتبها سيرجيو ميديروس وفابيو ماسكارينهاس.
تدعم الرسالة المخصصة %t
وهو عنصر نائب للرمز المميز غير المتوقع، و %c
لحرف Unicode غير المتوقع.
فيما يلي مثال على القواعد النحوية المشابهة لجافا:
# java.peg
Prog ← 'public' 'class' NAME '{' 'public' 'static' 'void' 'main' '(' 'String' '[' ']' NAME ')' BlockStmt '}'
BlockStmt ← '{' (!'}' Stmt^stmtb)* '}' # Annotated with `stmtb`
Stmt ← IfStmt / WhileStmt / PrintStmt / DecStmt / AssignStmt / BlockStmt
IfStmt ← 'if' '(' Exp ')' Stmt ('else' Stmt)?
WhileStmt ← 'while' '(' Exp^condw ')' Stmt # Annotated with `condw`
DecStmt ← 'int' NAME ('=' Exp)? ';'
AssignStmt ← NAME '=' Exp ';'^semia # Annotated with `semi`
PrintStmt ← 'System.out.println' '(' Exp ')' ';'
Exp ← RelExp ('==' RelExp)*
RelExp ← AddExp ('<' AddExp)*
AddExp ← MulExp (('+' / '-') MulExp)*
MulExp ← AtomExp (('*' / '/') AtomExp)*
AtomExp ← '(' Exp ')' / NUMBER / NAME
NUMBER ← < [0-9]+ >
NAME ← < [a-zA-Z_][a-zA-Z_0-9]* >
%whitespace ← [ tn]*
%word ← NAME
# Recovery operator labels
semia ← '' { error_message "missing semicolon in assignment." }
stmtb ← (!(Stmt / 'else' / '}') .)* { error_message "invalid statement" }
condw ← &'==' ('==' RelExp)* / &'<' ('<' AddExp)* / (!')' .)*
على سبيل المثال، ';'^semi
عبارة عن سكر نحوي لـ (';' / %recovery(semi))
. يحاول عامل %recover
استرداد الخطأ عند ';' عن طريق تخطي إدخال النص باستخدام تعبير الاسترداد semi
. كما ترتبط semi
برسالة مخصصة "فاصلة منقوطة مفقودة في المهمة".
وهنا النتيجة:
> نموذج فئة cat.javapublic مثال { public static void main(String[] args) {int n = 5;int f = 1;while( < n) { f = f * n; n = n - 1};System.out.println(f); } } > peglint java.peg Sample.javasample.java:5:12: خطأ في بناء الجملة، غير متوقع '<'، متوقع '('، <NUMBER>، <NAME>.sample.java:8:5: فاصلة منقوطة مفقودة في المهمة.sample .java:8:6: بيان غير صالح
كما ترون، يمكنه الآن إظهار أكثر من خطأ واحد، وتقديم رسائل خطأ ذات معنى أكثر من الرسائل الافتراضية.
يمكننا ربط رسائل الخطأ المخصصة بالتعريفات.
# custom_message.peg
START <- CODE (',' CODE)*
CODE <- < '0x' [a-fA-F0-9]+ > { error_message 'code format error...' }
%whitespace <- [ t]*
> cat custom_message.txt 0x1234,0x@@@@,0xABCD > peglint custom_message.peg custom_message.txt custom_message.txt:1:8: code format error...
ملاحظة: إذا كان هناك أكثر من عنصر يحتوي على تعليمات رسالة خطأ في الاختيار ذي الأولوية، فقد لا تعمل هذه الميزة كما تتوقع.
يمكننا تغيير قاعدة تعريف البداية على النحو التالي.
قواعد تلقائية = R"( Start <- A A <- B (',' B)* B <- '[one]' / '[two]' %مسافة بيضاء <- [ tn]*)"; peg::parser parser(grammar, "A"); // قاعدة البداية هي "أ" orpeg::محلل محلل; parser.load_grammar(grammar, "A"); // قاعدة البداية هي "A"parser.parse(" [one] , [two] "); // نعم
> cd lint > mkdir build > cd build > cmake .. > make > ./peglint usage: grammar_file_path [source_file_path] options: --source: source text --packrat: enable packrat memoise --ast: show AST tree --opt, --opt-all: optimize all AST nodes except nodes selected with `no_ast_opt` instruction --opt-only: optimize only AST nodes selected with `no_ast_opt` instruction --trace: show concise trace messages --profile: show profile report --verbose: verbose output for trace and profile
> cat a.peg Additive <- Multiplicative '+' Additive / Multiplicative Multiplicative <- Primary '*' Multiplicative / Primary Primary <- '(' Additive ')' / Number %whitespace <- [ trn]* > peglint a.peg [commandline]:3:35: 'Number' is not defined.
> cat a.peg Additive <- Multiplicative '+' Additive / Multiplicative Multiplicative <- Primary '*' Multiplicative / Primary Primary <- '(' Additive ')' / Number Number <- < [0-9]+ > %whitespace <- [ trn]* > peglint --source "1 + a * 3" a.peg [commandline]:1:3: syntax error
> cat a.txt 1 + 2 * 3 > peglint --ast a.peg a.txt + Additive + Multiplicative + Primary - Number (1) + Additive + Multiplicative + Primary - Number (2) + Multiplicative + Primary - Number (3)
> peglint --ast --opt --source "1 + 2 * 3" a.peg + Additive - Multiplicative[Number] (1) + Additive[Multiplicative] - Primary[Number] (2) - Multiplicative[Number] (3)
no_ast_opt
> cat a.peg Additive <- Multiplicative '+' Additive / Multiplicative Multiplicative <- Primary '*' Multiplicative / Primary Primary <- '(' Additive ')' / Number { no_ast_opt } Number <- < [0-9]+ > %whitespace <- [ trn]* > peglint --ast --opt --source "1 + 2 * 3" a.peg + Additive/0 + Multiplicative/1[Primary] - Number (1) + Additive/1[Multiplicative] + Primary/1 - Number (2) + Multiplicative/1[Primary] - Number (3) > peglint --ast --opt-only --source "1 + 2 * 3" a.peg + Additive/0 + Multiplicative/1 - Primary/1[Number] (1) + Additive/1 + Multiplicative/0 - Primary/1[Number] (2) + Multiplicative/1 - Primary/1[Number] (3)
آلة حاسبة
الآلة الحاسبة (مع مشغلي المحلل اللغوي)
الآلة الحاسبة (إصدار AST)
الآلة الحاسبة (تحليل التعبيرات حسب تسلق الأسبقية)
الآلة الحاسبة (إصدار AST وتحليل التعبيرات حسب تسلق الأسبقية)
مترجم PL/0 JIT صغير في أقل من 900 LOC مع محلل LLVM وPEG
لغة برمجة لكتابة برنامج Fizz Buzz فقط. :)
ترخيص معهد ماساتشوستس للتكنولوجيا (© 2022 يوجي هيروس)