Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------------------

Expand Down
40 changes: 34 additions & 6 deletions peglib.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<size_t>(-1);
}
}
} else {
if (c.log && !msg.empty() && c.error_info.message_pos < s) {
c.error_info.message_pos = s;
Expand Down Expand Up @@ -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<Error>(&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; }
Expand Down
92 changes: 92 additions & 0 deletions test/test1.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}