From 9b34bbecf591cbc832ce530a218e7053477e23a1 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Mon, 1 Dec 2025 13:32:57 -0800 Subject: [PATCH 1/3] Add peg::Error. --- peglib.h | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/peglib.h b/peglib.h index e3ff80e..7b75412 100644 --- a/peglib.h +++ b/peglib.h @@ -670,6 +670,16 @@ class Action { Fty fn_; }; +class Error { +public: + Error(std::string message) : message_(std::move(message)) {} + + const std::string& message() const { return message_; } + +private: + std::string message_; +}; + /* * Parse result helper */ @@ -1443,7 +1453,8 @@ class Holder : public Ope { void accept(Visitor &v) override; - std::any reduce(SemanticValues &vs, std::any &dt) const; + bool reduce(std::any &a_val, SemanticValues &vs, std::any &dt, + std::string& msg) const; const std::string &name() const; const std::string &trace_name() const; @@ -2843,7 +2854,16 @@ inline size_t Holder::parse_core(const char *s, size_t n, SemanticValues &vs, } if (success(len)) { - if (!c.recovered) { a_val = reduce(chvs, dt); } + if (!c.recovered) { + if (!reduce(a_val, chvs, dt, msg)) { + if (c.log && !msg.empty() && c.error_info.message_pos < s) { + c.error_info.message_pos = s; + c.error_info.message = msg; + c.error_info.label = outer_->name; + } + len = static_cast(-1); + } + } } else { if (c.log && !msg.empty() && c.error_info.message_pos < s) { c.error_info.message_pos = s; @@ -2871,14 +2891,22 @@ inline size_t Holder::parse_core(const char *s, size_t n, SemanticValues &vs, return len; } -inline std::any Holder::reduce(SemanticValues &vs, std::any &dt) const { +inline bool Holder::reduce(std::any &a_val, SemanticValues &vs, std::any &dt, + std::string &msg) const { if (outer_->action && !outer_->disable_action) { - return outer_->action(vs, dt); + auto unchecked = outer_->action(vs, dt); + if (const Error* err = std::any_cast(&unchecked)) { + msg = err->message(); + return false; + } + a_val = std::move(unchecked); + return true; } else if (vs.empty()) { - return std::any(); + a_val = std::any(); } else { - return std::move(vs.front()); + a_val = std::move(vs.front()); } + return true; } inline const std::string &Holder::name() const { return outer_->name; } From 1618c6c23cf1d7ae1db196fff0e3668ac50155f5 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 4 Dec 2025 12:14:44 -0800 Subject: [PATCH 2/3] Add tests. --- test/test1.cc | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/test/test1.cc b/test/test1.cc index cac6007..4e4f6a5 100644 --- a/test/test1.cc +++ b/test/test1.cc @@ -1329,3 +1329,95 @@ TEST(GeneralTest, InvalidRange) { EXPECT_FALSE(ret); } +namespace { + auto expect_log(size_t expected_ln, size_t expected_col, + std::string expected_msg) { + return [=](size_t ln, size_t col, const std::string &msg) { + EXPECT_EQ(expected_ln, ln); + EXPECT_EQ(expected_col, col); + EXPECT_EQ(expected_msg, msg); + }; + } +} + +TEST(GeneralTest, Action_with_Error) { + const std::string message = "Not allowing 'x'"; + + parser parser(R"( + WORD <- LETTER {5} + LETTER <- [a-z] + )"); + + parser["LETTER"] = [&](const SemanticValues &vs) -> std::any { + if (vs.token() == "x") { + return Error(message); + } + return vs.token(); + }; + + EXPECT_TRUE(parser.parse("hello")); + + parser.set_logger(expect_log(1, 2, message)); + EXPECT_FALSE(parser.parse("extra")); +} + +TEST(GeneralTest, Action_with_Error_choice) { + const std::string message = "Not allowing 'x'"; + + parser parser(R"( + WORD <- LETTER / '!' + LETTER <- [a-z] + )"); + + parser["LETTER"] = [&](const SemanticValues &vs) -> std::any { + if (vs.token() == "x") { + return Error(message); + } + return vs.token(); + }; + + parser.set_logger(expect_log(1, 1, message)); + EXPECT_FALSE(parser.parse("x")); +} + +TEST(GeneralTest, Action_with_Error_choice_overlap) { + const std::string message = "Not allowing 'x'"; + + parser parser(R"( + WORD <- LETTER / 'x' + LETTER <- [a-z] + )"); + + parser["LETTER"] = [&](const SemanticValues &vs) -> std::any { + if (vs.token() == "x") { + return Error(message); + } + return vs.token(); + }; + + EXPECT_TRUE(parser.parse("x")); +} + +TEST(GeneralTest, Action_with_Error_choice_2nd_error) { + const std::string message = "Not allowing 'x'"; + const std::string message2 = "2nd message"; + + parser parser(R"( + WORD <- LETTER / X + LETTER <- [a-z] + X <- 'x' + )"); + + parser["LETTER"] = [&](const SemanticValues &vs) -> std::any { + if (vs.token() == "x") { + return Error(message); + } + return vs.token(); + }; + parser["X"] = [&](const SemanticValues & /*vs*/) -> std::any { + return Error(message2); + }; + + parser.set_logger(expect_log(1, 1, message)); + EXPECT_FALSE(parser.parse("x")); +} From 456f75d2cc3e9f27fda7cea578189622e824209b Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 4 Dec 2025 12:22:54 -0800 Subject: [PATCH 3/3] Update readme. --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index e710401..a19ef14 100644 --- a/README.md +++ b/README.md @@ -647,6 +647,33 @@ custom_message.txt:1:8: code format error... NOTE: If there is more than one element with an error message instruction in a prioritized choice, this feature may not work as you expect. +Errors During Actions +--------------------- + +It is possible to return a `peg::Error` during an action. + +This can be used to check that a parsed number can be supported by the resulting integer type: +```cpp +parser parser(R"( + NUMBER <- [0-9]+ +)"); + +parser.number = [](const peg::SemanticValues& vs) -> std::any { + int value; + auto [ptr, err] = std::from_chars( + vs.token().data(), + vs.token().data() + vs.token().size(), + value + ); + + if (err != std::errc()) { + return peg::Error("Number out of range."); + } + + return value; +}; +``` + Change the Start Definition Rule --------------------------------