ไลบรารี PEG (Parsing Expression Grammars) เฉพาะส่วนหัว 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 (นิพจน์คำ)
$name( ... ) (ตัวดำเนินการขอบเขตการจับภาพ)
$name< ... > (ตัวดำเนินการจับภาพที่มีชื่อ)
$name (ตัวดำเนินการอ้างอิงกลับ)
| (ตัวดำเนินการพจนานุกรม)
↑ (ตัวดำเนินการตัด)
MACRO_NAME( ... ) (กฎที่มีพารามิเตอร์หรือมาโคร)
{ precedence L - + L / * } (การแยกวิเคราะห์นิพจน์มัด)
%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" ที่ยอดเยี่ยมนี้โดย bert Hubert!
นี่คือตัวอย่างเครื่องคิดเลขอย่างง่าย โดยจะแสดงวิธีกำหนดไวยากรณ์ เชื่อมโยงการดำเนินการทางความหมายกับไวยากรณ์ และจัดการค่าทางความหมาย
// (1) รวมไฟล์ส่วนหัว#include <peglib.h>#include <assert.h>#include <iostream>โดยใช้ namespace peg;โดยใช้ namespace std;int main(void) { // (2) สร้าง parser
parser parser(R"( # ไวยากรณ์สำหรับเครื่องคิดเลข... การบวก <- การคูณ '+' การบวก / การคูณ การคูณ <- หลัก '*' การคูณ / ระดับประถมศึกษา <- '(' การบวก ')' / หมายเลขตัวเลข <- < [ 0-9]+ > %ช่องว่าง <- [ t]* )"); assert(static_cast<bool>(parser) == true); // (3) การดำเนินการตั้งค่า
parser["Additive"] = [](const SemanticValues &vs) {switch (vs.choice()) {case 0: // "การคูณ '+' Additive" ส่งคืน any_cast<int>(vs[0]) + any_cast< int>(vs[1]);default: // "Multiplicative" return any_cast<int>(vs[0]);
-
-
parser["Multiplicative"] = [](const SemanticValues &vs) {switch (vs.choice()) {case 0: // "Primary '*' Multiplicative" ส่งคืน any_cast<int>(vs[0]) * any_cast< int>(vs[1]);default: // "Primary" return any_cast<int>(vs[0]);
-
-
parser ["หมายเลข"] = [] (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 << บรรทัด << marin << col << ": " << msg << "n";
});auto ok = parser.load_grammar(ไวยากรณ์);assert(ตกลง);มีการดำเนินการทางความหมายสี่รายการ:
[](const SemanticValues& vs, any& dt) [](const SemanticValues& เทียบกับ) [](ค่าความหมาย& เทียบกับ ใดๆ& dt) [](ค่าความหมายและเทียบกับ)
ค่า SemanticValues มีข้อมูลต่อไปนี้:
ค่าความหมาย
ข้อมูลสตริงที่ตรงกัน
ข้อมูลโทเค็นหากกฎเป็นตัวอักษรหรือใช้ตัวดำเนินการขอบเขตโทเค็น
หมายเลขตัวเลือกเมื่อกฎคือ 'ตัวเลือกที่มีลำดับความสำคัญ'
any& dt เป็นข้อมูลบริบท 'อ่าน-เขียน' ซึ่งสามารถใช้เพื่อวัตถุประสงค์ใดก็ได้ ข้อมูลบริบทเริ่มต้นถูกตั้งค่าในวิธี peg::parser::parse
การดำเนินการทางความหมายสามารถส่งคืนค่าของประเภทข้อมูลที่กำหนดเอง ซึ่งจะถูกห่อด้วย peg::any หากผู้ใช้ไม่ส่งคืนสิ่งใดในการดำเนินการเชิงความหมาย ค่าความหมายแรกในอาร์กิวเมนต์ const SemanticValues& vs จะถูกส่งคืน (ตัวแยกวิเคราะห์ Yacc มีพฤติกรรมเหมือนกัน)
ที่นี่แสดงโครงสร้าง SemanticValues :
struct SemanticValues : protected std::vector<any>
{ // ป้อนข้อความ
เส้นทาง const char*; const ถ่าน * เอสเอส; // สตริงที่ตรงกัน
std::string_view sv() const { กลับ sv_; } // หมายเลขบรรทัดและคอลัมน์ที่มีสตริงที่ตรงกัน
มาตรฐาน::คู่<size_t, size_t> line_info() const; //โทเค็น
std::vector<std::string_view> โทเค็น;
std::string_view โทเค็น (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() const; // แปลงเวกเตอร์ค่าความหมายเป็นเวกเตอร์อื่น
เทมเพลต <typename T> vector<T> การแปลง (size_t beg = 0, size_t end = -1) const;
- ตัวอย่างต่อไปนี้ใช้ตัวดำเนินการ < ... > ซึ่งเป็นตัวดำเนินการ ขอบเขตโทเค็น
หมุด::parser parser(R"( ROOT <- _ TOKEN (',' _ TOKEN)* TOKEN <- < [a-z0-9]+ > _ _ <- [ trn]*)");
parser["TOKEN"] = [](const SemanticValues& vs) { // 'token' ไม่รวมช่องว่างต่อท้าย
โทเค็นอัตโนมัติ = เทียบกับโทเค็น ();
};auto ret = parser.parse(" token1, token2 "); เราสามารถละเว้นค่าความหมายที่ไม่จำเป็นจากรายการได้โดยใช้ตัวดำเนินการ ~
หมุด::parser parser(R"( ROOT <- _ ITEM (',' _ ITEM _)* ITEM <- ([a-z0-9])+ ~_ <- [ t]*)");
parser["ROOT"] = [&](const SemanticValues& vs) { assert(vs.size() == 2); // ควรเป็น 2 แทนที่จะเป็น 5.};auto ret = parser.parse(" item1, item2 ");ไวยากรณ์ต่อไปนี้เหมือนกับข้างต้น
หมุด::parser parser(R"( ROOT <- ~_ ITEM (',' ~_ ITEM ~_)* ITEM <- ([a-z0-9])+ _ <- [ t]*)");การสนับสนุน เพรดิเคตความหมาย พร้อมใช้งานกับการดำเนินการ เพรดิเคต
หมุด::parser parser("หมายเลข <- [0-9]+");
parser["NUMBER"] = [](const SemanticValues &vs) { return vs.token_to_number<ยาว>();
-
parser["NUMBER"].predicate = [](const SemanticValues &vs,const std::any & /*dt*/, std::string &msg) { ถ้า (vs.token_to_number<ยาว>() != 100) {
msg = "ค่าผิดพลาด!!";คืนค่าเท็จ;
} คืนค่าจริง;
};long val;auto ret = parser.parse("100", val);assert(ret == true);assert(val == 100);
ret = parser.parse("200", val);assert(ret == false);การดำเนินการ เข้า และ ออก ก็มีให้เช่นกัน
parser["RULE"].enter = [](const Context &c, const char* s, size_t n, ใดๆ& dt) {
std::cout << "ป้อน" << std::endl;
-
parser["RULE"] = [](const SemanticValues& vs, any& dt) {
std::cout << "การกระทำ!" << มาตรฐาน::endl;
-
parser["RULE"].leave = [](const Context &c, const char* s, size_t n, size_t matchlen, ใด ๆ& ค่า, ใด ๆ& dt) {
std::cout << "ออก" << std::endl;
-คุณสามารถรับข้อมูลข้อผิดพลาดผ่านตัวบันทึก:
parser.set_logger([](บรรทัด size_t, size_t col, สตริง const& msg) {
-
-
parser.set_logger ([] (บรรทัด size_t, size_t col, สตริง const& msg, สตริง const & กฎ) {
-
- ดังที่คุณเห็นในตัวอย่างแรก เราสามารถละเว้นช่องว่างระหว่างโทเค็นโดยอัตโนมัติด้วยกฎ %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' %ช่องว่าง <- [ trn]* %word <- [az]+)");
parser.parse("สวัสดีชาวโลก"); // OKparser.parse("helloworld"); // ง peg::parser parser(R"( ROOT <- เนื้อหาเนื้อหา <- (ELEMENT / TEXT)* ELEMENT <- $(STAG CONTENT ETAG) STAG <- '<' $tag< TAG_NAME > '>' ETAG <- '< /' $tag '>' TAG_NAME <- 'b' / 'u' TEXT <- TEXT_DATA TEXT_DATA <- -
parser.parse("นี่คือ <b>a <u>ทดสอบ</u> ข้อความ</b>."); // OKparser.parse("นี่คือ <b>a <u>ทดสอบ</b> ข้อความ</u>."); // NGparser.parse("นี่คือ <b>a <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 ]+ > %ช่องว่าง <- [ t]* # ประกาศลำดับความสำคัญ INFIX_EXPRESSION(A, O) <- A (O A)* { ลำดับความสำคัญ L + - L * / })");
parser["INFIX_EXPRESSION"] = [](const SemanticValues& vs) -> long { ผลลัพธ์อัตโนมัติ = 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 '+': ผลลัพธ์ += หมายเลข; หยุดพัก; กรณี '-': ผลลัพธ์ -= num; หยุดพัก; กรณี '*': ผลลัพธ์ *= num; หยุดพัก; กรณี '/': ผลลัพธ์ /= num; หยุดพัก;
-
} ส่งคืนผลลัพธ์;
-
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", วาล);ยืนยัน(วาล == 9);คำสั่ง ลำดับความสำคัญ สามารถใช้ได้กับกฎสไตล์ 'รายการ' ต่อไปนี้เท่านั้น
Rule <- Atom (Operator Atom)* {
precedence
L - +
L / *
R ^
}คำสั่ง ลำดับความสำคัญ ประกอบด้วยรายการข้อมูลลำดับความสำคัญ แต่ละรายการเริ่มต้นด้วย การเชื่อมโยง ซึ่งก็คือ 'L' (ซ้าย) หรือ 'R' (ขวา) จากนั้นโทเค็น ตามตัวอักษร ของตัวดำเนินการจะตามมา รายการแรกมีระดับการสั่งซื้อสูงสุด
cpp-peglib สามารถสร้าง AST (Abstract Syntax Tree) เมื่อแยกวิเคราะห์ เมธอด 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
แทนที่จะสร้าง parser โดยการแยกวิเคราะห์ข้อความไวยากรณ์ PEG เรายังสามารถสร้าง parser ด้วยมือโดยใช้ parser combinators นี่คือตัวอย่าง:
การใช้เนมสเปซ peg; การใช้เนมสเปซ std; vector<string> แท็ก;
คำจำกัดความ ROOT, TAG_NAME, _;
ROOT <= seq(_, zom(seq(chr('['), TAG_NAME, chr(']'), _)));
TAG_NAME <= oom(seq(npd(chr(']')), dot())), [&](const SemanticValues& เทียบกับ) {
tags.push_back(เทียบกับtoken_to_string());
-
_ <= zom(cls(" t"));auto ret = ROOT.parse(" [tag1] [tag:2] [tag-3] ");ตัวดำเนินการที่พร้อมใช้งานต่อไปนี้:
| ผู้ดำเนินการ | คำอธิบาย | ผู้ดำเนินการ | คำอธิบาย |
|---|---|---|---|
| ลำดับ | ลำดับ | โช | ทางเลือกที่จัดลำดับความสำคัญ |
| โซม | ศูนย์หรือมากกว่า | อุม | หนึ่งหรือมากกว่า |
| เลือก | ไม่จำเป็น | บวก | และภาคแสดง |
| เอ็นพีดี | ไม่ใช่ภาคแสดง | สว่าง | สตริงตัวอักษร |
| ลิติ | สตริงตัวอักษรที่ไม่คำนึงถึงขนาดตัวพิมพ์ | cl | คลาสตัวละคร |
| ไม่มี | คลาสตัวละครที่ถูกปฏิเสธ | ค | อักขระ |
| จุด | ตัวละครอะไรก็ได้ | ต๊อก | ขอบเขตโทเค็น |
| ติดไฟ | ละเว้นค่าความหมาย | ซีเอสซี | ขอบเขตการจับภาพ |
| หมวก | การจับกุม | บีเคอาร์ | อ้างอิงกลับ |
| ดิ๊ก | พจนานุกรม | ก่อน | นิพจน์มัด |
| รับ | นิพจน์มัด | เรา | ตัวแยกวิเคราะห์ที่ผู้ใช้กำหนด |
| ตัวแทน | การทำซ้ำ |
สามารถเพิ่ม/แทนที่คำจำกัดความได้
ไวยากรณ์อัตโนมัติ = R"( ROOT <- _ 'Hello' _ NAME '!' _)";
กฎเพิ่มเติม_กฎ = {
{"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; // แยกวิเคราะห์ข้อผิดพลาด})
-
{"~_", zom(cls(" trn"))
-
};auto g = parser(syntax, added_rules);assert(g.parse(" Hello BNF! ")); cpp-peglib ยอมรับข้อความ UTF8 . ตรงกับจุดรหัส Unicode นอกจากนี้ยังสนับสนุน u???? -
cpp-peglib รองรับรายงานตำแหน่งข้อผิดพลาดความล้มเหลวที่ไกลที่สุดตามที่อธิบายไว้ในเอกสารต้นฉบับของ Bryan Ford
เพื่อการรายงานข้อผิดพลาดและการกู้คืนที่ดีขึ้น cpp-peglib รองรับตัวดำเนินการ 'การกู้คืน' พร้อมป้ายกำกับซึ่งสามารถเชื่อมโยงกับนิพจน์การกู้คืนและข้อความแสดงข้อผิดพลาดที่กำหนดเอง แนวคิดนี้มาจากบทความ "การกู้คืนข้อผิดพลาดทางไวยากรณ์ในการแยกวิเคราะห์ไวยากรณ์นิพจน์" ที่ยอดเยี่ยมโดย Sergio Medeiros และ Fabio Mascarenhas
ข้อความที่กำหนดเองรองรับ %t ซึ่งเป็นตัวยึดตำแหน่งสำหรับโทเค็นที่ไม่คาดคิด และ %c สำหรับอักขระ Unicode ที่ไม่คาดคิด
นี่คือตัวอย่างไวยากรณ์ที่คล้ายกับ Java:
# 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 { โมฆะสาธารณะคง main (String [] args) {int n = 5;int f = 1; ในขณะที่ ( < n) { f = f * n; n = n - 1};System.out.println(f);
-
-
> peglint java.peg example.javasample.java:5:12: ข้อผิดพลาดทางไวยากรณ์ '<' ที่ไม่คาดคิด คาดหวัง '(', <NUMBER>, <NAME>.sample.java:8:5: เครื่องหมายอัฒภาคหายไปใน allowance.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"( เริ่ม <- A A <- B (',' B)* B <- '[หนึ่ง]' / '[สอง]' %ช่องว่าง <- [ tn]*)";
peg::parser parser(ไวยากรณ์, "A"); // กฎการเริ่มต้นคือ "A"
orpeg::ตัวแยกวิเคราะห์ ตัวแยกวิเคราะห์;
parser.load_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 เท่านั้น -
ใบอนุญาต MIT (© 2022 Yuji Hirose)