Skip to content
Merged
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
15 changes: 15 additions & 0 deletions src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,21 @@ auto to_pointer(const std::basic_string<JSON::Char, JSON::CharTraits,
SOURCEMETA_CORE_JSONPOINTER_EXPORT
auto to_pointer(const WeakPointer &pointer) -> Pointer;

/// @ingroup jsonpointer
/// Check if the given string is a valid JSON Pointer per RFC 6901 without
/// constructing a JSON Pointer object. For example:
///
/// ```cpp
/// #include <sourcemeta/core/jsonpointer.h>
/// #include <cassert>
///
/// assert(sourcemeta::core::is_pointer("/foo/bar/0"));
/// assert(sourcemeta::core::is_pointer(""));
/// assert(!sourcemeta::core::is_pointer("foo"));
/// ```
SOURCEMETA_CORE_JSONPOINTER_EXPORT
auto is_pointer(std::string_view input) noexcept -> bool;

/// @ingroup jsonpointer
/// Convert a JSON Pointer into a JSON WeakPointer. For example:
///
Expand Down
13 changes: 12 additions & 1 deletion src/core/jsonpointer/jsonpointer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <ostream> // std::basic_ostream
#include <sstream> // std::basic_ostringstream, std::basic_stringstream
#include <string> // std::basic_string
#include <string_view> // std::string_view
#include <type_traits> // std::is_same_v
#include <utility> // std::move

Expand Down Expand Up @@ -302,7 +303,7 @@ auto remove(JSON &document, const WeakPointer &pointer) -> bool {
auto to_pointer(const JSON &document) -> Pointer {
assert(document.is_string());
auto stream{document.to_stringstream()};
return parse_pointer(stream);
return parse_pointer<false>(stream);
}

auto to_pointer(const std::basic_string<JSON::Char, JSON::CharTraits,
Expand Down Expand Up @@ -407,4 +408,14 @@ auto to_uri(const WeakPointer &pointer, const std::string_view base) -> URI {
return to_uri(pointer).resolve_from(URI{base}).canonicalize();
}

auto is_pointer(const std::string_view input) noexcept -> bool {
try {
std::basic_istringstream<JSON::Char> stream{std::string{input}};
parse_pointer<true>(stream);
return true;
} catch (...) {
return false;
}
}

} // namespace sourcemeta::core
94 changes: 64 additions & 30 deletions src/core/jsonpointer/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
#include <sourcemeta/core/jsonpointer.h>
#include <sourcemeta/core/jsonpointer_error.h>

#include <cstdint> // std::uint64_t
#include <istream> // std::basic_istream
#include <sstream> // std::basic_stringstream
#include <stdexcept> // std::out_of_range
#include <string> // std::stoi
#include <cstdint> // std::uint64_t
#include <istream> // std::basic_istream
#include <sstream> // std::basic_stringstream
#include <stdexcept> // std::out_of_range
#include <string> // std::stoi
#include <type_traits> // std::conditional_t

namespace sourcemeta::core::internal {
template <typename CharT, typename Traits,
Expand Down Expand Up @@ -41,11 +42,12 @@ parse_index(std::basic_stringstream<CharT, Traits, Allocator<CharT>> &stream,
// NOLINTBEGIN(cppcoreguidelines-avoid-goto)

namespace sourcemeta::core {
template <bool CheckOnly>
auto parse_pointer(std::basic_istream<JSON::Char, JSON::CharTraits> &stream)
-> Pointer {
Pointer result;
-> std::conditional_t<CheckOnly, void, Pointer> {
[[maybe_unused]] Pointer result;
JSON::Char character = 0;
std::basic_stringstream<JSON::Char> string;
[[maybe_unused]] std::basic_stringstream<JSON::Char> string;
std::uint64_t column{0};

parse_token_begin:
Expand Down Expand Up @@ -84,15 +86,21 @@ auto parse_pointer(std::basic_istream<JSON::Char, JSON::CharTraits> &stream)
case internal::token_pointer_number_nine<JSON::Char>:
column += 1;
stream.ignore();
string.put(character);
if constexpr (!CheckOnly) {
string.put(character);
}
goto parse_token_index_rest_any;
case static_cast<JSON::Char>(JSON::CharTraits::eof()):
column += 1;
stream.ignore();
result.emplace_back("");
if constexpr (!CheckOnly) {
result.emplace_back("");
}
goto done;
case internal::token_pointer_slash<JSON::Char>:
result.emplace_back("");
if constexpr (!CheckOnly) {
result.emplace_back("");
}
goto parse_token_begin;
case internal::token_pointer_tilde<JSON::Char>:
column += 1;
Expand All @@ -101,7 +109,9 @@ auto parse_pointer(std::basic_istream<JSON::Char, JSON::CharTraits> &stream)
default:
column += 1;
stream.ignore();
string.put(character);
if constexpr (!CheckOnly) {
string.put(character);
}
goto parse_token_property_rest_any;
}

Expand All @@ -110,20 +120,26 @@ auto parse_pointer(std::basic_istream<JSON::Char, JSON::CharTraits> &stream)
*/

parse_token_index_end:
string.put(character);
if constexpr (!CheckOnly) {
string.put(character);
}
character = static_cast<JSON::Char>(stream.peek());
switch (character) {
case internal::token_pointer_slash<JSON::Char>:
column += 1;
stream.ignore();
result.emplace_back(internal::parse_index(string, column));
internal::reset(string);
if constexpr (!CheckOnly) {
result.emplace_back(internal::parse_index(string, column));
internal::reset(string);
}
goto parse_token_content;
case static_cast<JSON::Char>(JSON::CharTraits::eof()):
column += 1;
stream.ignore();
result.emplace_back(internal::parse_index(string, column));
internal::reset(string);
if constexpr (!CheckOnly) {
result.emplace_back(internal::parse_index(string, column));
internal::reset(string);
}
goto done;
default:
goto parse_token_property_rest_any;
Expand All @@ -135,14 +151,18 @@ auto parse_pointer(std::basic_istream<JSON::Char, JSON::CharTraits> &stream)
case internal::token_pointer_slash<JSON::Char>:
column += 1;
stream.ignore();
result.emplace_back(internal::parse_index(string, column));
internal::reset(string);
if constexpr (!CheckOnly) {
result.emplace_back(internal::parse_index(string, column));
internal::reset(string);
}
goto parse_token_content;
case static_cast<JSON::Char>(JSON::CharTraits::eof()):
column += 1;
stream.ignore();
result.emplace_back(internal::parse_index(string, column));
internal::reset(string);
if constexpr (!CheckOnly) {
result.emplace_back(internal::parse_index(string, column));
internal::reset(string);
}
goto done;
case internal::token_pointer_number_zero<JSON::Char>:
case internal::token_pointer_number_one<JSON::Char>:
Expand All @@ -156,7 +176,9 @@ auto parse_pointer(std::basic_istream<JSON::Char, JSON::CharTraits> &stream)
case internal::token_pointer_number_nine<JSON::Char>:
column += 1;
stream.ignore();
string.put(character);
if constexpr (!CheckOnly) {
string.put(character);
}
goto parse_token_index_rest_any;

default:
Expand All @@ -172,17 +194,23 @@ auto parse_pointer(std::basic_istream<JSON::Char, JSON::CharTraits> &stream)
column += 1;
switch (character) {
case internal::token_pointer_slash<JSON::Char>:
result.emplace_back(string.str());
internal::reset(string);
if constexpr (!CheckOnly) {
result.emplace_back(string.str());
internal::reset(string);
}
goto parse_token_content;
case internal::token_pointer_tilde<JSON::Char>:
goto parse_token_escape_tilde;
case static_cast<JSON::Char>(JSON::CharTraits::eof()):
result.emplace_back(string.str());
internal::reset(string);
if constexpr (!CheckOnly) {
result.emplace_back(string.str());
internal::reset(string);
}
goto done;
default:
string.put(character);
if constexpr (!CheckOnly) {
string.put(character);
}
goto parse_token_property_rest_any;
}

Expand All @@ -196,17 +224,23 @@ auto parse_pointer(std::basic_istream<JSON::Char, JSON::CharTraits> &stream)
// See https://www.rfc-editor.org/rfc/rfc6901#section-3
switch (character) {
case internal::token_pointer_number_zero<JSON::Char>:
string.put(internal::token_pointer_tilde<JSON::Char>);
if constexpr (!CheckOnly) {
string.put(internal::token_pointer_tilde<JSON::Char>);
}
goto parse_token_property_rest_any;
case internal::token_pointer_number_one<JSON::Char>:
string.put(internal::token_pointer_slash<JSON::Char>);
if constexpr (!CheckOnly) {
string.put(internal::token_pointer_slash<JSON::Char>);
}
goto parse_token_property_rest_any;
default:
throw PointerParseError(column);
}

done:
return result;
if constexpr (!CheckOnly) {
return result;
}
}

// NOLINTEND(cppcoreguidelines-avoid-goto)
Expand Down
Loading
Loading