This commit is contained in:
yhirose 2021-01-21 20:56:05 -05:00
parent d4bcc53178
commit 80b20a091f
12 changed files with 62 additions and 64 deletions

View File

@ -408,7 +408,12 @@ AST generation
*cpp-peglib* is able to generate an AST (Abstract Syntax Tree) when parsing. `enable_ast` method on `peg::parser` class enables the feature. *cpp-peglib* is able to generate an AST (Abstract Syntax Tree) when parsing. `enable_ast` method on `peg::parser` class enables the feature.
``` ```
peg::parser parser("..."); peg::parser parser(R"(
...
defenition1 <- ... { no_ast_opt }
defenition2 <- ... { no_ast_opt }
...
)");
parser.enable_ast(); parser.enable_ast();
@ -416,14 +421,14 @@ shared_ptr<peg::Ast> ast;
if (parser.parse("...", ast)) { if (parser.parse("...", ast)) {
cout << peg::ast_to_s(ast); cout << peg::ast_to_s(ast);
std::vector<std::string> exceptions = { "defenition1", "defenition2 }; ast = parser.optimize_ast(ast);
ast = peg::AstOptimizer(true, exceptions).optimize(ast);
cout << peg::ast_to_s(ast); cout << peg::ast_to_s(ast);
} }
``` ```
`peg::AstOptimizer` removes redundant nodes to make a AST simpler. You can make your own AST optimizers to fit your needs. `optimize_ast` removes redundant nodes to make a AST simpler. If you want to disable this behavior from particular rules, `no_ast_opt` instruction can be used.
It internally calls `peg::AstOptimizer` to do the job. You can make your own AST optimizers to fit your needs.
See actual usages in the [AST calculator example](https://github.com/yhirose/cpp-peglib/blob/master/example/calc3.cc) and [PL/0 language example](https://github.com/yhirose/cpp-peglib/blob/master/pl0/pl0.cc). See actual usages in the [AST calculator example](https://github.com/yhirose/cpp-peglib/blob/master/example/calc3.cc) and [PL/0 language example](https://github.com/yhirose/cpp-peglib/blob/master/pl0/pl0.cc).

View File

@ -23,8 +23,7 @@
<div class="editor-sub-header">AST</div> <div class="editor-sub-header">AST</div>
<pre id="code-ast" class="editor-area"></pre> <pre id="code-ast" class="editor-area"></pre>
<div class="editor-sub-header">Optimized AST&nbsp;&nbsp;&nbsp;&nbsp; <div class="editor-sub-header">Optimized AST&nbsp;&nbsp;&nbsp;&nbsp;
mode:&nbsp;<select id="opt_mode" type="checkbox"><option value="all">All</option><option value="only">Only</option></select>&nbsp;&nbsp; mode:&nbsp;<select id="opt_mode" type="checkbox"><option value="all">All</option><option value="only">Only</option></select>
rules:&nbsp;<input id="opt_rules" type="text" size="40" placeholder="Enter definition rules separated by comma."></input>
</div> </div>
<pre id="code-ast-optimized" class="editor-area"></pre> <pre id="code-ast-optimized" class="editor-area"></pre>
<div id="code-info" class="editor-info"></div> <div id="code-info" class="editor-info"></div>

View File

@ -28,7 +28,6 @@ codeAstOptimized.setOptions({
codeAstOptimized.renderer.$cursorLayer.element.style.opacity=0; codeAstOptimized.renderer.$cursorLayer.element.style.opacity=0;
$('#opt_mode').val(localStorage.getItem('optimazationMode') || 'all'); $('#opt_mode').val(localStorage.getItem('optimazationMode') || 'all');
$('#opt_rules').val(localStorage.getItem('optimazationRules'));
function escapeHtml(unsafe) { function escapeHtml(unsafe) {
return unsafe return unsafe
@ -61,12 +60,10 @@ function parse() {
const codeText = code.getValue(); const codeText = code.getValue();
const optimazationMode = $('#opt_mode').val(); const optimazationMode = $('#opt_mode').val();
const optimazationRules = $('#opt_rules').val();
localStorage.setItem('grammarText', grammarText); localStorage.setItem('grammarText', grammarText);
localStorage.setItem('codeText', codeText); localStorage.setItem('codeText', codeText);
localStorage.setItem('optimazationMode', optimazationMode); localStorage.setItem('optimazationMode', optimazationMode);
localStorage.setItem('optimazationRules', optimazationRules);
$grammarInfo.html(''); $grammarInfo.html('');
$grammarValidation.hide(); $grammarValidation.hide();
@ -80,7 +77,7 @@ function parse() {
} }
const mode = optimazationMode == 'all'; const mode = optimazationMode == 'all';
const data = JSON.parse(Module.lint(grammarText, codeText, mode, optimazationRules)); const data = JSON.parse(Module.lint(grammarText, codeText, mode));
const isValid = data.grammar.length === 0; const isValid = data.grammar.length === 0;
if (isValid) { if (isValid) {
@ -126,7 +123,6 @@ $('#code-info').on('click', 'li', makeOnClickInInfo(code));
// Event handing in the AST optimazation // Event handing in the AST optimazation
$('#opt_mode').on('change', setupTimer); $('#opt_mode').on('change', setupTimer);
$('#opt_rules').on('keyup', setupTimer);
// Show page // Show page
$('#main').css({ $('#main').css({

View File

@ -53,20 +53,7 @@ void parse_code(const std::string &text, peg::parser &peg, std::string &json,
json += "]"; json += "]";
} }
inline std::vector<std::string> splitRulesText(const std::string &s) { std::string lint(const std::string &grammarText, const std::string &codeText, bool opt_mode) {
std::vector<std::string> elems;
std::stringstream ss(s);
std::string elem;
while (getline(ss, elem, ',')) {
if (!elem.empty()) {
elems.push_back(elem);
}
}
return elems;
}
std::string lint(const std::string &grammarText, const std::string &codeText,
bool opt_mode, const std::string &opt_rules_text) {
std::string grammarResult; std::string grammarResult;
std::string codeResult; std::string codeResult;
std::string astResult; std::string astResult;
@ -80,9 +67,8 @@ std::string lint(const std::string &grammarText, const std::string &codeText,
parse_code(codeText, peg, codeResult, ast); parse_code(codeText, peg, codeResult, ast);
if (ast) { if (ast) {
astResult = escape_json(peg::ast_to_s(ast)); astResult = escape_json(peg::ast_to_s(ast));
auto rules = splitRulesText(opt_rules_text);
astResultOptimized = escape_json( astResultOptimized = escape_json(
peg::ast_to_s(peg::AstOptimizer(opt_mode, rules).optimize(ast))); peg::ast_to_s(peg.optimize_ast(ast, opt_mode)));
} }
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -54,7 +54,7 @@ int main(int argc, const char **argv) {
auto expr = argv[1]; auto expr = argv[1];
std::shared_ptr<Ast> ast; std::shared_ptr<Ast> ast;
if (parser.parse(expr, ast)) { if (parser.parse(expr, ast)) {
ast = AstOptimizer(true).optimize(ast); ast = parser.optimize_ast(ast);
std::cout << ast_to_s(ast); std::cout << ast_to_s(ast);
std::cout << expr << " = " << eval(*ast) << std::endl; std::cout << expr << " = " << eval(*ast) << std::endl;
return 0; return 0;

View File

@ -54,7 +54,7 @@ int main(int argc, const char **argv) {
auto expr = argv[1]; auto expr = argv[1];
std::shared_ptr<Ast> ast; std::shared_ptr<Ast> ast;
if (parser.parse(expr, ast)) { if (parser.parse(expr, ast)) {
ast = AstOptimizer(true).optimize(ast); ast = parser.optimize_ast(ast);
std::cout << ast_to_s(ast); std::cout << ast_to_s(ast);
std::cout << expr << " = " << eval(*ast) << std::endl; std::cout << expr << " = " << eval(*ast) << std::endl;
return 0; return 0;

View File

@ -35,7 +35,6 @@ inline vector<string> split(const string &s, char delim) {
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
auto opt_ast = false; auto opt_ast = false;
auto opt_optimize = false; auto opt_optimize = false;
auto opt_mode = true;
vector<string> opt_rules; vector<string> opt_rules;
auto opt_help = false; auto opt_help = false;
auto opt_source = false; auto opt_source = false;
@ -50,17 +49,8 @@ int main(int argc, const char **argv) {
opt_help = true; opt_help = true;
} else if (string("--ast") == arg) { } else if (string("--ast") == arg) {
opt_ast = true; opt_ast = true;
} else if (string("--opt") == arg || string("--opt-all") == arg) { } else if (string("--opt") == arg) {
opt_optimize = true; opt_optimize = true;
opt_mode = true;
} else if (string("--opt-only") == arg) {
opt_optimize = true;
opt_mode = false;
} else if (string("--opt-rules") == arg) {
if (argi < argc) {
std::string s = argv[argi++];
opt_rules = split(s, ',');
}
} else if (string("--source") == arg) { } else if (string("--source") == arg) {
opt_source = true; opt_source = true;
if (argi < argc) { if (argi < argc) {
@ -190,7 +180,7 @@ int main(int argc, const char **argv) {
if (ast) { if (ast) {
if (opt_optimize) { if (opt_optimize) {
ast = peg::AstOptimizer(opt_mode, opt_rules).optimize(ast); ast = parser.optimize_ast(ast);
} }
std::cout << peg::ast_to_s(ast); std::cout << peg::ast_to_s(ast);
} }

View File

@ -2287,6 +2287,7 @@ public:
bool disable_action = false; bool disable_action = false;
std::string error_message; std::string error_message;
bool no_ast_opt = false;
private: private:
friend class Reference; friend class Reference;
@ -3058,10 +3059,10 @@ private:
~g["COMMA"] <= seq(chr(','), g["Spacing"]); ~g["COMMA"] <= seq(chr(','), g["Spacing"]);
// Instruction grammars // Instruction grammars
g["Instruction"] <= g["Instruction"] <= seq(g["BeginBlacket"],
seq(g["BeginBlacket"], cho(cho(g["PrecedenceClimbing"]),
cho(cho(g["PrecedenceClimbing"]), cho(g["ErrorMessage"])), cho(g["ErrorMessage"]), cho(g["NoAstOpt"])),
g["EndBlacket"]); g["EndBlacket"]);
~g["SpacesZom"] <= zom(g["Space"]); ~g["SpacesZom"] <= zom(g["Space"]);
~g["SpacesOom"] <= oom(g["Space"]); ~g["SpacesOom"] <= oom(g["Space"]);
@ -3084,6 +3085,9 @@ private:
g["ErrorMessage"] <= g["ErrorMessage"] <=
seq(lit("message"), g["SpacesOom"], g["LiteralD"], g["SpacesZom"]); seq(lit("message"), g["SpacesOom"], g["LiteralD"], g["SpacesZom"]);
// No Ast node optimazation instruction
g["NoAstOpt"] <= seq(lit("no_ast_opt"), g["SpacesZom"]);
// Set definition names // Set definition names
for (auto &x : g) { for (auto &x : g) {
x.second.name = x.first; x.second.name = x.first;
@ -3403,6 +3407,12 @@ private:
instruction.data = std::any_cast<std::string>(vs[0]); instruction.data = std::any_cast<std::string>(vs[0]);
return instruction; return instruction;
}; };
g["NoAstOpt"] = [](const SemanticValues & /*vs*/) {
Instruction instruction;
instruction.type = "no_ast_opt";
return instruction;
};
} }
bool apply_precedence_instruction(Definition &rule, bool apply_precedence_instruction(Definition &rule,
@ -3618,6 +3628,8 @@ private:
} }
} else if (instruction.type == "message") { } else if (instruction.type == "message") {
rule.error_message = std::any_cast<std::string>(instruction.data); rule.error_message = std::any_cast<std::string>(instruction.data);
} else if (instruction.type == "no_ast_opt") {
rule.no_ast_opt = true;
} }
} }
@ -4035,11 +4047,10 @@ public:
const Definition &operator[](const char *s) const { return (*grammar_)[s]; } const Definition &operator[](const char *s) const { return (*grammar_)[s]; }
std::vector<std::string> get_rule_names() { std::vector<std::string> get_rule_names() const {
std::vector<std::string> rules; std::vector<std::string> rules;
rules.reserve(grammar_->size()); for (auto &[name, _] : *grammar_) {
for (auto const &r : *grammar_) { rules.push_back(name);
rules.emplace_back(r.first);
} }
return rules; return rules;
} }
@ -4051,14 +4062,6 @@ public:
} }
} }
template <typename T = Ast> parser &enable_ast() {
for (auto &x : *grammar_) {
auto &rule = x.second;
if (!rule.action) { add_ast_action<T>(rule); }
}
return *this;
}
void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave) { void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave) {
if (grammar_ != nullptr) { if (grammar_ != nullptr) {
auto &rule = (*grammar_)[start_]; auto &rule = (*grammar_)[start_];
@ -4067,6 +4070,17 @@ public:
} }
} }
template <typename T = Ast> parser &enable_ast() {
for (auto &[_, rule] : *grammar_) {
if (!rule.action) { add_ast_action<T>(rule); }
}
return *this;
}
template <typename T> std::shared_ptr<T> optimize_ast(std::shared_ptr<T> ast, bool opt_mode = true) const {
return AstOptimizer(opt_mode, get_no_ast_opt_rules()).optimize(ast);
}
Log log; Log log;
private: private:
@ -4077,6 +4091,14 @@ private:
return ret && !r.recovered; return ret && !r.recovered;
} }
std::vector<std::string> get_no_ast_opt_rules() const {
std::vector<std::string> rules;
for (auto &[name, rule] : *grammar_) {
if (rule.no_ast_opt) { rules.push_back(name); }
}
return rules;
}
std::shared_ptr<Grammar> grammar_; std::shared_ptr<Grammar> grammar_;
std::string start_; std::string start_;
}; };

View File

@ -657,7 +657,7 @@ TEST_CASE("Calculator test with AST", "[general]") {
std::shared_ptr<Ast> ast; std::shared_ptr<Ast> ast;
auto ret = parser.parse("1+2*3*(4-5+6)/7-8", ast); auto ret = parser.parse("1+2*3*(4-5+6)/7-8", ast);
ast = AstOptimizer(true).optimize(ast); ast = parser.optimize_ast(ast);
auto val = eval(*ast); auto val = eval(*ast);
REQUIRE(ret == true); REQUIRE(ret == true);

View File

@ -1124,7 +1124,7 @@ CATEGORY <- < [-_a-zA-Z0-9\u0080-\uFFFF ]+ > _
ATTRIBUTES <- ATTRIBUTE (',' _ ATTRIBUTE)* ATTRIBUTES <- ATTRIBUTE (',' _ ATTRIBUTE)*
ATTRIBUTE <- < [-_a-zA-Z0-9\u0080-\uFFFF]+ > _ ATTRIBUTE <- < [-_a-zA-Z0-9\u0080-\uFFFF]+ > _
ENTRIES <- (ENTRY (__ ENTRY)*)? ENTRIES <- (ENTRY (__ ENTRY)*)? { no_ast_opt }
ENTRY <- ONE_WAY PHRASE ('|' _ PHRASE)* !'=' ENTRY <- ONE_WAY PHRASE ('|' _ PHRASE)* !'='
/ PHRASE ('|' _ PHRASE)+ !'=' / PHRASE ('|' _ PHRASE)+ !'='
@ -1187,7 +1187,7 @@ rrr | sss
)", ast)); )", ast));
ast = peg::AstOptimizer(true, {"ENTRIES"}).optimize(ast); ast = pg.optimize_ast(ast);
REQUIRE(ast_to_s(ast) == REQUIRE(ast_to_s(ast) ==
R"(+ START R"(+ START
@ -1275,7 +1275,7 @@ TEST_CASE("Error recovery 2", "[error]") {
REQUIRE_FALSE(pg.parse(R"([000]],[111],[222z,"aaa,"bbb",ccc"],[ddd",444,555,"eee],[ REQUIRE_FALSE(pg.parse(R"([000]],[111],[222z,"aaa,"bbb",ccc"],[ddd",444,555,"eee],[
)", ast)); )", ast));
ast = peg::AstOptimizer(true, {"ENTRIES"}).optimize(ast); ast = pg.optimize_ast(ast);
REQUIRE(ast_to_s(ast) == REQUIRE(ast_to_s(ast) ==
R"(+ START R"(+ START
@ -1386,7 +1386,7 @@ SkipToRCUR ← (!RCUR (LCUR SkipToRCUR / .))* RCUR
} }
)", ast)); )", ast));
ast = peg::AstOptimizer(true, {"ENTRIES"}).optimize(ast); ast = pg.optimize_ast(ast);
REQUIRE(ast_to_s(ast) == REQUIRE(ast_to_s(ast) ==
R"(+ Prog R"(+ Prog