diff --git a/src/core/jsonschema/frame.cc b/src/core/jsonschema/frame.cc index 96cedbd7a..82e5cdfe0 100644 --- a/src/core/jsonschema/frame.cc +++ b/src/core/jsonschema/frame.cc @@ -125,11 +125,9 @@ auto find_anchors(const sourcemeta::core::JSON &schema, return result; } -template -auto find_nearest_bases_ref( - const std::unordered_map> &bases, - const sourcemeta::core::WeakPointer &pointer) +template +auto find_nearest_bases_ref(const MapType &bases, + const sourcemeta::core::WeakPointer &pointer) -> std::optional< std::pair>, sourcemeta::core::WeakPointer>> { @@ -150,14 +148,12 @@ auto find_nearest_bases_ref( return std::nullopt; } -template -auto find_nearest_bases( - const std::unordered_map> &bases, - const sourcemeta::core::WeakPointer &pointer, - const std::optional &default_base) +template +auto find_nearest_bases(const MapType &bases, + const sourcemeta::core::WeakPointer &pointer, + const std::optional &default_base) -> std::pair, sourcemeta::core::WeakPointer> { - const auto result{find_nearest_bases_ref(bases, pointer)}; + const auto result{find_nearest_bases_ref(bases, pointer)}; if (result.has_value()) { return {result->first.get(), result->second}; } @@ -170,21 +166,37 @@ auto find_nearest_bases( return {{}, sourcemeta::core::empty_weak_pointer}; } -auto find_every_base( - const std::unordered_map> - &bases, - const sourcemeta::core::WeakPointer &pointer) - -> std::vector> { +template struct CombinedWalkResult { + std::optional< + std::pair>, + sourcemeta::core::WeakPointer>> + dialect_match; std::vector> - result; + every_base; +}; + +template +auto find_dialect_and_all_bases(const DialectMapType &base_dialects, + const BaseMapType &base_uris, + const sourcemeta::core::WeakPointer &pointer) + -> CombinedWalkResult { + CombinedWalkResult result; auto current_pointer{pointer}; while (true) { - const auto match{bases.find(current_pointer)}; - if (match != bases.cend()) { - for (const auto &base : match->second) { - result.emplace_back(std::string_view{base}, current_pointer); + if (!result.dialect_match.has_value()) { + const auto dialect_it{base_dialects.find(current_pointer)}; + if (dialect_it != base_dialects.cend()) { + result.dialect_match = + std::make_pair(std::cref(dialect_it->second), current_pointer); + } + } + + const auto base_it{base_uris.find(current_pointer)}; + if (base_it != base_uris.cend()) { + for (const auto &base : base_it->second) { + result.every_base.emplace_back(std::string_view{base}, current_pointer); } } @@ -195,10 +207,10 @@ auto find_every_base( current_pointer = current_pointer.initial(); } - if (result.empty() || - result.back().second != sourcemeta::core::empty_weak_pointer) { - result.emplace_back(std::string_view{}, - sourcemeta::core::empty_weak_pointer); + if (result.every_base.empty() || + result.every_base.back().second != sourcemeta::core::empty_weak_pointer) { + result.every_base.emplace_back(std::string_view{}, + sourcemeta::core::empty_weak_pointer); } return result; @@ -439,9 +451,9 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, assert(std::unordered_set(paths.cbegin(), paths.cend()).size() == paths.size()); std::vector subschema_entries; - std::unordered_map subschemas; - std::unordered_map> base_uris; - std::unordered_map> base_dialects; + std::map subschemas; + std::map> base_uris; + std::map> base_dialects; for (const auto &path : paths) { // Passing paths that overlap is undefined behavior. No path should @@ -547,7 +559,7 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, // identifier, we ignore it as a traditional identifier and take // care of it as an anchor !is_pre_2019_09_location_independent_identifier) { - const auto bases{find_nearest_bases( + const auto bases{find_nearest_bases( base_uris, common_pointer_weak, entry.id ? std::optional{*entry.id} : std::nullopt)}; @@ -613,7 +625,7 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, sourcemeta::core::dialect(entry.common.subschema.get())}; if (!maybe_metaschema.empty()) { sourcemeta::core::URI metaschema{maybe_metaschema}; - const auto nearest_bases{find_nearest_bases( + const auto nearest_bases{find_nearest_bases( base_uris, common_pointer_weak, entry.id ? std::optional{*entry.id} : std::nullopt)}; @@ -639,7 +651,7 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, // Handle schema anchors for (const auto &[name, type] : find_anchors(entry.common.subschema.get(), entry.common.vocabularies)) { - const auto bases{find_nearest_bases( + const auto bases{find_nearest_bases( base_uris, common_pointer_weak, entry.id ? std::optional{*entry.id} : std::nullopt)}; @@ -742,13 +754,41 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, for (const auto &relative_pointer : pointers) { const auto pointer_weak{path.concat(relative_pointer)}; - const auto dialect_match{ - find_nearest_bases_ref(base_dialects, pointer_weak)}; - const auto &dialect_for_pointer{dialect_match.has_value() - ? dialect_match->first.get().front() - : root_dialect}; + const auto combined{find_dialect_and_all_bases( + base_dialects, base_uris, pointer_weak)}; + const auto &dialect_for_pointer{ + combined.dialect_match.has_value() + ? combined.dialect_match->first.get().front() + : root_dialect}; + const auto &every_base_result{combined.every_base}; + + std::optional> nearest_base_info; + for (const auto &entry : every_base_result) { + if (!entry.first.empty()) { + nearest_base_info = entry; + break; + } + } - auto every_base_result = find_every_base(base_uris, pointer_weak); + const auto subschema_it{subschemas.find(pointer_weak)}; + const bool is_subschema{subschema_it != subschemas.cend()}; + const auto nearest_base_depth = + nearest_base_info.has_value() ? nearest_base_info->second.size() : 0; + + std::string_view hoisted_base_view{}; + sourcemeta::core::SchemaBaseDialect hoisted_base_dialect{}; + if (nearest_base_info.has_value()) { + const JSON::String nearest_base_str{nearest_base_info->first}; + const auto base_entry{this->locations_.find( + {SchemaReferenceType::Static, nearest_base_str})}; + if (base_entry != this->locations_.cend()) { + hoisted_base_view = base_entry->first.second; + hoisted_base_dialect = base_entry->second.base_dialect; + } else { + hoisted_base_view = nearest_base_info->first; + hoisted_base_dialect = root_base_dialect.value(); + } + } WeakPointer cached_base{}; for (const auto &base : every_base_result) { @@ -769,39 +809,39 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, this->locations_.contains({SchemaReferenceType::Static, result}); if (!contains) { - const auto nearest_bases{ - find_nearest_bases(base_uris, pointer_weak, - std::optional{base.first})}; - assert(!nearest_bases.first.empty()); - const auto ¤t_base{nearest_bases.first.front()}; - - const auto base_entry{this->locations_.find( - {SchemaReferenceType::Static, current_base})}; - - const std::string_view base_view{ - base_entry != this->locations_.cend() - ? std::string_view{base_entry->first.second} - : std::string_view{current_base}}; - - const sourcemeta::core::SchemaBaseDialect current_base_dialect{ - base_entry != this->locations_.cend() - ? base_entry->second.base_dialect - : root_base_dialect.value()}; - - const auto subschema{subschemas.find(pointer_weak)}; - if (subschema != subschemas.cend()) { + std::string_view base_view; + sourcemeta::core::SchemaBaseDialect current_base_dialect; + + if (nearest_base_info.has_value()) { + base_view = hoisted_base_view; + current_base_dialect = hoisted_base_dialect; + } else { + const JSON::String current_base{base.first}; + const auto base_entry{this->locations_.find( + {SchemaReferenceType::Static, current_base})}; + if (base_entry != this->locations_.cend()) { + base_view = base_entry->first.second; + current_base_dialect = base_entry->second.base_dialect; + } else { + base_view = base.first; + current_base_dialect = root_base_dialect.value(); + } + } + + if (is_subschema) { store(this->locations_, SchemaReferenceType::Static, SchemaFrame::LocationType::Subschema, std::move(result), - base_view, pointer_weak, nearest_bases.second.size(), + base_view, pointer_weak, nearest_base_depth, dialect_for_pointer, current_base_dialect, - subschema->second.parent, false, true); + subschema_it->second.parent, false, true); } else { store(this->locations_, SchemaReferenceType::Static, SchemaFrame::LocationType::Pointer, std::move(result), - base_view, pointer_weak, nearest_bases.second.size(), + base_view, pointer_weak, nearest_base_depth, dialect_for_pointer, current_base_dialect, - dialect_match.has_value() ? dialect_match->second - : empty_weak_pointer, + combined.dialect_match.has_value() + ? combined.dialect_match->second + : empty_weak_pointer, false, true); } } @@ -817,7 +857,7 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, for (const auto &entry : subschema_entries) { const auto &common_pointer_weak{entry.common.pointer}; if (entry.common.subschema.get().is_object()) { - const auto nearest_bases{find_nearest_bases( + const auto nearest_bases{find_nearest_bases( base_uris, common_pointer_weak, entry.id ? std::optional{*entry.id} : std::nullopt)};