mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 02:37:13 +00:00
Derive the lexer position lazily for seekable input (#5809)
* Derive the lexer position lazily for seekable input The lexer counted the read position on every character: two increments and a newline check per get(), all on members kept in memory. For seekable input (pointers, strings, other random-access byte ranges) the position can be derived instead: chars_read_total is the adapter's read count (including reads at EOF) minus a pending unget, and line and column are recomputed from the consumed input only when get_position() is called, i.e. on parse errors. JSON_DIAGNOSTIC_POSITIONS uses the new O(1) get_chars_read_total(). Stream, file, wide-string and user-defined adapters keep the eager counters. Parsing pretty-printed input gets 8-12% faster and accept() 12-27% (canada, citm_catalog, twitter; clang 21 arm64 and GCC 13 x86-64). Error messages, byte offsets, SAX positions and diagnostic positions are unchanged (differential comparison against develop on 548k inputs). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Move the std::vector<std::byte> binary type tests out of unit-regression3 The #4804/#4209 tests instantiated a second basic_json specialization (with_binary_t<std::vector<std::byte>>), including its parser and CBOR reader. With clang 18, the C++20 object of unit-regression3.cpp had 32656 COFF sections on develop; the lazy position helpers pushed it to 32975, past what the MinGW linker of the Windows clang jobs handles ("relocation truncated to fit: IMAGE_REL_AMD64_REL32 against .rdata"). unit-custom-binary-type.cpp already instantiates the same type, so the tests move there; unit-regression3.cpp drops to 27616 sections. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Report the parse errors of the position test via SAX The test caught json::parse_error, so it aborted in the ci_test_noexceptions build, where JSON_NOEXCEPTION turns the throw into std::abort(). sax_parse() hands the error position and the exception object to parse_error() without throwing, so the test now records them there and runs in every configuration. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
40a62947d3
commit
f23830b65e
9 files changed
+380
-105
No files matched your search
@@ -35,6 +35,7 @@ using nlohmann::json;
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <valarray>
|
||||
#include <vector>
|
||||
|
||||
#include "sax_event_loggers.hpp"
|
||||
|
||||
@@ -1250,3 +1251,66 @@ TEST_CASE_TEMPLATE("deserialization of different character types (UTF-32)", T, c
|
||||
CHECK(json::sax_parse(v, &l));
|
||||
CHECK(l.events.size() == 1);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// records the position and message of a parse error; reporting the error via
|
||||
// SAX does not throw, so the test also runs with JSON_NOEXCEPTION
|
||||
struct SaxErrorRecorder : public SaxEventLogger
|
||||
{
|
||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& ex) override
|
||||
{
|
||||
error = std::to_string(position) + " " + ex.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string error {}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("parse error positions are the same for contiguous and stream input")
|
||||
{
|
||||
// contiguous input derives the position from the input on error, stream
|
||||
// input counts it while reading (see lexer::lazy_position); the cases
|
||||
// cover newlines before the error, ungetting a newline after a number,
|
||||
// newlines inside a string, a comment, and reads at the end of input
|
||||
struct test_case
|
||||
{
|
||||
std::string input;
|
||||
std::size_t byte;
|
||||
std::string message;
|
||||
};
|
||||
const std::vector<test_case> cases =
|
||||
{
|
||||
{"[\n 1,\n 2\n", 12, "[json.exception.parse_error.101] parse error at line 4, column 1: syntax error while parsing array - unexpected end of input; expected ']'"},
|
||||
{"[1\n,2,\n]", 8, "[json.exception.parse_error.101] parse error at line 3, column 1: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal"},
|
||||
{"[1\n x]", 5, "[json.exception.parse_error.101] parse error at line 2, column 2: syntax error while parsing array - invalid literal; last read: '1<U+000A> x'; expected ']'"},
|
||||
{"1\n2", 3, "[json.exception.parse_error.101] parse error at line 2, column 1: syntax error while parsing value - unexpected number literal; expected end of input"},
|
||||
{
|
||||
"{\"a\":\n\"b\nc\"}", 9, R"([json.exception.parse_error.101] parse error at line 3, column 0: syntax error while parsing value - invalid string: control character U+000A (LF) must be escaped to \u000A or \n; last read: '"b<U+000A>')"
|
||||
},
|
||||
{"/* a\n b */\n x", 13, "[json.exception.parse_error.101] parse error at line 3, column 2: syntax error while parsing value - invalid literal; last read: '/* a<U+000A> b */<U+000A> x'"},
|
||||
{"\n\n", 3, "[json.exception.parse_error.101] parse error at line 3, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal"},
|
||||
{"[\"a\",\n 1e\n]", 10, "[json.exception.parse_error.101] parse error at line 3, column 0: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e<U+000A>'"},
|
||||
{"[tru\n]", 5, "[json.exception.parse_error.101] parse error at line 2, column 0: syntax error while parsing value - invalid literal; last read: '[tru<U+000A>'"},
|
||||
};
|
||||
|
||||
for (const auto& c : cases)
|
||||
{
|
||||
CAPTURE(c.input)
|
||||
const std::string expected = std::to_string(c.byte) + " " + c.message;
|
||||
|
||||
SaxErrorRecorder string_sax;
|
||||
CHECK_FALSE(json::sax_parse(c.input, &string_sax, json::input_format_t::json, true, true));
|
||||
CHECK(string_sax.error == expected);
|
||||
|
||||
SaxErrorRecorder pointer_sax;
|
||||
CHECK_FALSE(json::sax_parse(c.input.data(), c.input.data() + c.input.size(), &pointer_sax, json::input_format_t::json, true, true));
|
||||
CHECK(pointer_sax.error == expected);
|
||||
|
||||
std::istringstream is(c.input);
|
||||
SaxErrorRecorder stream_sax;
|
||||
CHECK_FALSE(json::sax_parse(is, &stream_sax, json::input_format_t::json, true, true));
|
||||
CHECK(stream_sax.error == expected);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user