Remove some of the state handoff via ivars within Parser.

Hopefully makes things a litte easier to follow.
This commit is contained in:
Thomas Van Lenten 2021-09-30 16:32:47 -04:00
parent a7823bbd45
commit a9e1d7c177
2 changed files with 79 additions and 50 deletions

View File

@ -1638,70 +1638,69 @@ class Parser {
Parser(LineConsumer* line_consumer) Parser(LineConsumer* line_consumer)
: line_consumer_(line_consumer), line_(0) {} : line_consumer_(line_consumer), line_(0) {}
// Parses a check of input, returning success/failure. // Feeds in some input, prase what it can, returning success/failure. Calling
bool ParseChunk(StringPiece chunk); // again after an error is undefined.
bool ParseChunk(StringPiece chunk, std::string* out_error);
// Should be called to finish parsing (after all input has been provided via // Should be called to finish parsing (after all input has been provided via
// ParseChunk()). Returns success/failure. // successful calls to ParseChunk(), calling after a ParseChunk() failure is
bool Finish(); // undefined). Returns success/failure.
bool Finish(std::string* out_error);
int last_line() const { return line_; } int last_line() const { return line_; }
std::string error_str() const { return error_str_; }
private: private:
bool ParseLoop();
LineConsumer* line_consumer_; LineConsumer* line_consumer_;
int line_; int line_;
std::string error_str_;
StringPiece p_;
std::string leftover_; std::string leftover_;
}; };
bool Parser::ParseChunk(StringPiece chunk) { bool Parser::ParseChunk(StringPiece chunk, std::string* out_error) {
StringPiece full_chunk;
if (!leftover_.empty()) { if (!leftover_.empty()) {
leftover_ += std::string(chunk); leftover_ += std::string(chunk);
p_ = StringPiece(leftover_); full_chunk = StringPiece(leftover_);
} else { } else {
p_ = chunk; full_chunk = chunk;
}
bool result = ParseLoop();
if (p_.empty()) {
leftover_.clear();
} else {
leftover_ = std::string(p_);
}
return result;
} }
bool Parser::Finish() {
// If there is still something to go, flush it with a newline.
if (!leftover_.empty() && !ParseChunk("\n")) {
return false;
}
// This really should never fail if ParseChunk succeeded, but check to be sure.
return leftover_.empty();
}
bool Parser::ParseLoop() {
StringPiece line; StringPiece line;
while (ReadLine(&p_, &line)) { while (ReadLine(&full_chunk, &line)) {
++line_; ++line_;
RemoveComment(&line); RemoveComment(&line);
TrimWhitespace(&line); TrimWhitespace(&line);
if (line.empty()) { if (!line.empty() && !line_consumer_->ConsumeLine(line, out_error)) {
continue; // Blank line. if (out_error->empty()) {
*out_error = "ConsumeLine failed without setting an error.";
} }
if (!line_consumer_->ConsumeLine(line, &error_str_)) { leftover_.clear();
return false; return false;
} }
} }
if (full_chunk.empty()) {
leftover_.clear();
} else {
leftover_ = std::string(full_chunk);
}
return true; return true;
} }
std::string ParserErrorString(const Parser& parser, const std::string& name) { bool Parser::Finish(std::string* out_error) {
return std::string("error: ") + name + " Line " + // If there is still something to go, flush it with a newline.
StrCat(parser.last_line()) + ", " + parser.error_str(); if (!leftover_.empty() && !ParseChunk("\n", out_error)) {
return false;
}
// This really should never fail if ParseChunk succeeded, but check to be sure.
if (!leftover_.empty()) {
*out_error = "ParseSimple Internal error: finished with pending data.";
return false;
}
return true;
}
std::string FullErrorString(const std::string& name, int line_num, const std::string& msg) {
return std::string("error: ") + name + " Line " + StrCat(line_num) + ", " + msg;
} }
} // namespace } // namespace
@ -1731,6 +1730,7 @@ bool ParseSimpleStream(io::ZeroCopyInputStream& input_stream,
const std::string& stream_name, const std::string& stream_name,
LineConsumer* line_consumer, LineConsumer* line_consumer,
std::string* out_error) { std::string* out_error) {
std::string local_error;
Parser parser(line_consumer); Parser parser(line_consumer);
const void* buf; const void* buf;
int buf_len; int buf_len;
@ -1739,13 +1739,14 @@ bool ParseSimpleStream(io::ZeroCopyInputStream& input_stream,
continue; continue;
} }
if (!parser.ParseChunk(StringPiece(static_cast<const char*>(buf), buf_len))) { if (!parser.ParseChunk(StringPiece(static_cast<const char*>(buf), buf_len),
*out_error = ParserErrorString(parser, stream_name); &local_error)) {
*out_error = FullErrorString(stream_name, parser.last_line(), local_error);
return false; return false;
} }
} }
if (!parser.Finish()) { if (!parser.Finish(&local_error)) {
*out_error = ParserErrorString(parser, stream_name); *out_error = FullErrorString(stream_name, parser.last_line(), local_error);
return false; return false;
} }
return true; return true;

View File

@ -246,13 +246,16 @@ TEST(ObjCHelperDeathTest, TextFormatDecodeData_Failures) {
class TestLineCollector : public LineConsumer { class TestLineCollector : public LineConsumer {
public: public:
TestLineCollector(std::vector<std::string>* inout_lines, TestLineCollector(std::vector<std::string>* inout_lines,
const std::string* reject_line = nullptr) const std::string* reject_line = nullptr,
: lines_(inout_lines), reject_(reject_line) {} bool skip_msg = false)
: lines_(inout_lines), reject_(reject_line), skip_msg_(skip_msg) {}
bool ConsumeLine(const StringPiece& line, std::string* out_error) override { bool ConsumeLine(const StringPiece& line, std::string* out_error) override {
if (reject_) { if (reject_) {
if (*reject_ == line) { if (*reject_ == line) {
if (!skip_msg_) {
*out_error = std::string("Rejected '") + *reject_ + "'"; *out_error = std::string("Rejected '") + *reject_ + "'";
}
return false; return false;
} }
} }
@ -265,6 +268,7 @@ class TestLineCollector : public LineConsumer {
private: private:
std::vector<std::string>* lines_; std::vector<std::string>* lines_;
const std::string* reject_; const std::string* reject_;
bool skip_msg_;
}; };
const int kBlockSizes[] = {-1, 1, 2, 5, 64}; const int kBlockSizes[] = {-1, 1, 2, 5, 64};
@ -325,10 +329,10 @@ TEST(ObjCHelper, ParseSimple_DropsComments) {
TEST(ObjCHelper, ParseSimple_RejectLines) { TEST(ObjCHelper, ParseSimple_RejectLines) {
const std::vector<std::tuple<std::string, std::string, int>> tests = { const std::vector<std::tuple<std::string, std::string, int>> tests = {
{"a\nb\nc", "a", 1}, std::make_tuple("a\nb\nc", "a", 1),
{"a\nb\nc", "b", 2}, std::make_tuple("a\nb\nc", "b", 2),
{"a\nb\nc", "c", 3}, std::make_tuple("a\nb\nc", "c", 3),
{"a\nb\nc\n", "c", 3}, std::make_tuple("a\nb\nc\n", "c", 3),
}; };
for (const auto& test : tests) { for (const auto& test : tests) {
@ -338,7 +342,31 @@ TEST(ObjCHelper, ParseSimple_RejectLines) {
std::string err_str; std::string err_str;
TestLineCollector collector(nullptr, &std::get<1>(test)); TestLineCollector collector(nullptr, &std::get<1>(test));
EXPECT_FALSE(ParseSimpleStream(input, "dummy", &collector, &err_str)); EXPECT_FALSE(ParseSimpleStream(input, "dummy", &collector, &err_str));
std::string expected_err = StrCat("error: dummy Line ", std::get<2>(test), ", Rejected '", std::get<1>(test), "'"); std::string expected_err =
StrCat("error: dummy Line ", std::get<2>(test), ", Rejected '", std::get<1>(test), "'");
EXPECT_EQ(err_str, expected_err);
}
}
}
TEST(ObjCHelper, ParseSimple_RejectLinesNoMessage) {
const std::vector<std::tuple<std::string, std::string, int>> tests = {
std::make_tuple("a\nb\nc", "a", 1),
std::make_tuple("a\nb\nc", "b", 2),
std::make_tuple("a\nb\nc", "c", 3),
std::make_tuple("a\nb\nc\n", "c", 3),
};
for (const auto& test : tests) {
for (int i = 0; i < kBlockSizeCount; i++) {
io::ArrayInputStream input(std::get<0>(test).data(), std::get<0>(test).size(),
kBlockSizes[i]);
std::string err_str;
TestLineCollector collector(nullptr, &std::get<1>(test), true /* skip msg */);
EXPECT_FALSE(ParseSimpleStream(input, "dummy", &collector, &err_str));
std::string expected_err =
StrCat("error: dummy Line ", std::get<2>(test),
", ConsumeLine failed without setting an error.");
EXPECT_EQ(err_str, expected_err); EXPECT_EQ(err_str, expected_err);
} }
} }