|
14 | 14 | #include <utility> // std::move, std::pair |
15 | 15 | #include <vector> // std::vector |
16 | 16 |
|
| 17 | +#if defined(SOURCEMETA_BLAZE_DEBUG_TRACK_FLAGS) |
| 18 | +#include <cstdio> // stderr |
| 19 | +#include <functional> // std::function |
| 20 | +#include <print> // std::println |
| 21 | +#endif |
| 22 | + |
17 | 23 | #include "compile_helpers.h" |
18 | 24 | #include "postprocess.h" |
19 | 25 |
|
@@ -147,6 +153,138 @@ auto schema_frame_populate_target_types( |
147 | 153 | } |
148 | 154 | } |
149 | 155 |
|
| 156 | +auto is_evaluation_mark_instruction( |
| 157 | + const sourcemeta::blaze::InstructionIndex type) -> bool { |
| 158 | + using sourcemeta::blaze::InstructionIndex; |
| 159 | + switch (type) { |
| 160 | + case InstructionIndex::AssertionArrayPrefixEvaluate: |
| 161 | + case InstructionIndex::AssertionPropertyTypeEvaluate: |
| 162 | + case InstructionIndex::AssertionPropertyTypeStrictEvaluate: |
| 163 | + case InstructionIndex::AssertionPropertyTypeStrictAnyEvaluate: |
| 164 | + case InstructionIndex::Evaluate: |
| 165 | + case InstructionIndex::LogicalNotEvaluate: |
| 166 | + case InstructionIndex::LoopPropertiesUnevaluated: |
| 167 | + case InstructionIndex::LoopPropertiesUnevaluatedExcept: |
| 168 | + case InstructionIndex::LoopPropertiesEvaluate: |
| 169 | + case InstructionIndex::LoopPropertiesTypeEvaluate: |
| 170 | + case InstructionIndex::LoopPropertiesTypeStrictEvaluate: |
| 171 | + case InstructionIndex::LoopPropertiesTypeStrictAnyEvaluate: |
| 172 | + case InstructionIndex::LoopItemsUnevaluated: |
| 173 | + case InstructionIndex::ControlEvaluate: |
| 174 | + return true; |
| 175 | + default: |
| 176 | + return false; |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +auto contains_evaluation_marks(const sourcemeta::blaze::Instructions &steps, |
| 181 | + const std::vector<bool> &target_marks, |
| 182 | + const bool dynamic_marks) -> bool { |
| 183 | + using sourcemeta::blaze::InstructionIndex; |
| 184 | + for (const auto &step : steps) { |
| 185 | + if (is_evaluation_mark_instruction(step.type)) { |
| 186 | + return true; |
| 187 | + } |
| 188 | + |
| 189 | + if (step.type == InstructionIndex::ControlJump && |
| 190 | + target_marks[std::get<sourcemeta::blaze::ValueUnsignedInteger>( |
| 191 | + step.value)]) { |
| 192 | + return true; |
| 193 | + } |
| 194 | + |
| 195 | + if (step.type == InstructionIndex::ControlDynamicAnchorJump && |
| 196 | + dynamic_marks) { |
| 197 | + return true; |
| 198 | + } |
| 199 | + |
| 200 | + if (contains_evaluation_marks(step.children, target_marks, dynamic_marks)) { |
| 201 | + return true; |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + return false; |
| 206 | +} |
| 207 | + |
| 208 | +auto flag_evaluation_marks(sourcemeta::blaze::Instructions &steps, |
| 209 | + const std::vector<bool> &target_marks, |
| 210 | + const bool dynamic_marks) -> bool { |
| 211 | + using sourcemeta::blaze::InstructionIndex; |
| 212 | + bool result{false}; |
| 213 | + for (auto &step : steps) { |
| 214 | + step.track = |
| 215 | + is_evaluation_mark_instruction(step.type) || |
| 216 | + (step.type == InstructionIndex::ControlJump && |
| 217 | + target_marks[std::get<sourcemeta::blaze::ValueUnsignedInteger>( |
| 218 | + step.value)]) || |
| 219 | + (step.type == InstructionIndex::ControlDynamicAnchorJump && |
| 220 | + dynamic_marks); |
| 221 | + if (flag_evaluation_marks(step.children, target_marks, dynamic_marks)) { |
| 222 | + step.track = true; |
| 223 | + } |
| 224 | + |
| 225 | + result = result || step.track; |
| 226 | + } |
| 227 | + |
| 228 | + return result; |
| 229 | +} |
| 230 | + |
| 231 | +// Determine which instructions can produce or consume evaluation marks |
| 232 | +// somewhere in their subtree, including through static and dynamic jumps, |
| 233 | +// so that the evaluator only maintains the evaluate path on the spines that |
| 234 | +// lead to those instructions. Note that the evaluate path of every mark and |
| 235 | +// of every mark consumer is preserved exactly: all of their ancestors are |
| 236 | +// flagged, and only instructions that no mark or consumer can ever observe |
| 237 | +// stop pushing |
| 238 | +auto flag_evaluation_tracking( |
| 239 | + std::vector<sourcemeta::blaze::Instructions> &targets, |
| 240 | + const std::vector<std::pair<std::size_t, std::size_t>> &labels) -> void { |
| 241 | + std::vector<bool> target_marks(targets.size(), false); |
| 242 | + bool dynamic_marks{false}; |
| 243 | + bool changed{true}; |
| 244 | + while (changed) { |
| 245 | + changed = false; |
| 246 | + for (std::size_t index = 0; index < targets.size(); index++) { |
| 247 | + if (!target_marks[index] && |
| 248 | + contains_evaluation_marks(targets[index], target_marks, |
| 249 | + dynamic_marks)) { |
| 250 | + target_marks[index] = true; |
| 251 | + changed = true; |
| 252 | + } |
| 253 | + } |
| 254 | + |
| 255 | + if (!dynamic_marks && |
| 256 | + std::ranges::any_of(labels, [&target_marks](const auto &label) { |
| 257 | + return target_marks[label.second]; |
| 258 | + })) { |
| 259 | + dynamic_marks = true; |
| 260 | + changed = true; |
| 261 | + } |
| 262 | + } |
| 263 | + |
| 264 | + for (auto &target : targets) { |
| 265 | + flag_evaluation_marks(target, target_marks, dynamic_marks); |
| 266 | + } |
| 267 | + |
| 268 | +#if defined(SOURCEMETA_BLAZE_DEBUG_TRACK_FLAGS) |
| 269 | + std::size_t flagged{0}; |
| 270 | + std::size_t total{0}; |
| 271 | + // NOLINTNEXTLINE(misc-no-recursion) |
| 272 | + const std::function<void(const sourcemeta::blaze::Instructions &)> count{ |
| 273 | + [&flagged, &total, &count](const sourcemeta::blaze::Instructions &steps) { |
| 274 | + for (const auto &step : steps) { |
| 275 | + total += 1; |
| 276 | + flagged += step.track ? 1 : 0; |
| 277 | + count(step.children); |
| 278 | + } |
| 279 | + }}; |
| 280 | + for (const auto &target : targets) { |
| 281 | + count(target); |
| 282 | + } |
| 283 | + |
| 284 | + std::println(stderr, "TRACK FLAGS: {} / {}", flagged, total); |
| 285 | +#endif |
| 286 | +} |
| 287 | + |
150 | 288 | } // namespace |
151 | 289 |
|
152 | 290 | namespace sourcemeta::blaze { |
@@ -421,6 +559,11 @@ auto compile(const sourcemeta::core::JSON &schema, |
421 | 559 | context.unevaluated, [](const auto &dependency) -> auto { |
422 | 560 | return dependency.first.ends_with("unevaluatedItems"); |
423 | 561 | })}; |
| 562 | + |
| 563 | + if (track) { |
| 564 | + flag_evaluation_tracking(compiled_targets, labels_map); |
| 565 | + } |
| 566 | + |
424 | 567 | return {.dynamic = uses_dynamic_scopes, |
425 | 568 | .track = track, |
426 | 569 | .targets = std::move(compiled_targets), |
|
0 commit comments