hecks 4.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +1 -0
- data/exe/hecks +15 -3
- data/lib/hecks/adapters/driven/claude_code.rb +1 -1
- data/lib/hecks/adapters/driven/d1.rb +2 -1
- data/lib/hecks/adapters/driven/folder.rb +35 -28
- data/lib/hecks/adapters/driven/folder_overlay.rb +51 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +99 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +32 -36
- data/lib/hecks/adapters/driven/heki/journal.rb +45 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +1 -1
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -2
- data/lib/hecks/adapters/driven/key_keeper.rb +42 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +8 -2
- data/lib/hecks/adapters/driven/lambda/remote_dispatcher.rb +185 -0
- data/lib/hecks/adapters/driven/lambda.rb +14 -2
- data/lib/hecks/adapters/driven/memory/dispatch_claims.rb +25 -0
- data/lib/hecks/adapters/driven/memory.rb +15 -4
- data/lib/hecks/adapters/driven/postgres/codec.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/journal.rb +46 -0
- data/lib/hecks/adapters/driven/postgres/outbox.rb +31 -5
- data/lib/hecks/adapters/driven/postgres/projection.rb +43 -0
- data/lib/hecks/adapters/driven/postgres/repository.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +2 -1
- data/lib/hecks/adapters/driven/postgres/set_dialect.rb +44 -0
- data/lib/hecks/adapters/driven/postgres.adapter +8 -0
- data/lib/hecks/adapters/driven/postgres.rb +8 -3
- data/lib/hecks/adapters/driven/postgres_dump/connection.rb +57 -0
- data/lib/hecks/adapters/driven/postgres_dump.rb +117 -0
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/boot.rb +6 -3
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/class_methods.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/dialect.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/chain_sql.rb +8 -4
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/domain_rename.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_integrity.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_store.rb +3 -3
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/mint_transaction.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/sql_templates.rb +6 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/context.rb +15 -5
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/coverage_check.rb +13 -11
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/era_resolver.rb +23 -6
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/merge_coordinator.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/minter.rb +12 -9
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/refusals.rb +13 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager.rb +6 -6
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/query_phases.rb +8 -2
- data/lib/hecks/adapters/driven/postgres_era.adapter +7 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era.rb +14 -13
- data/lib/hecks/adapters/driven/prism.rb +4 -3
- data/lib/hecks/adapters/driven/projection/read_model_walk.rb +126 -0
- data/lib/hecks/adapters/driven/projection/row_decoding.rb +57 -0
- data/lib/hecks/adapters/driven/set_transform/assignments.rb +101 -0
- data/lib/hecks/adapters/driven/set_transform/expressions.rb +138 -0
- data/lib/hecks/adapters/driven/set_transform.rb +81 -0
- data/lib/hecks/adapters/driven/sql_keyset.rb +61 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +28 -9
- data/lib/hecks/adapters/driven/sqlite/codec.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/ddl.rb +7 -0
- data/lib/hecks/adapters/driven/sqlite/dialect.rb +2 -1
- data/lib/hecks/adapters/driven/sqlite/dispatch_claims.rb +28 -0
- data/lib/hecks/adapters/driven/sqlite/journal.rb +46 -0
- data/lib/hecks/adapters/driven/sqlite/outbox.rb +3 -2
- data/lib/hecks/adapters/driven/sqlite/projection.rb +9 -135
- data/lib/hecks/adapters/driven/sqlite/repository.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +16 -4
- data/lib/hecks/adapters/driven/sqlite/set_dialect.rb +39 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +2 -0
- data/lib/hecks/adapters/driven/sqlite.rb +11 -2
- data/lib/hecks/adapters/driven.rb +8 -3
- data/lib/hecks/adapters/driving/cli.rb +1 -1
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +23 -16
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +25 -24
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +9 -5
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +1 -1
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +15 -7
- data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +49 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +4 -2
- data/lib/hecks/adapters/driving/github_webhook.rb +2 -1
- data/lib/hecks/adapters/driving/handle/verb_methods.rb +46 -0
- data/lib/hecks/adapters/driving/handle.rb +9 -17
- data/lib/hecks/adapters/driving/json.rb +19 -17
- data/lib/hecks/adapters/driving/launcher_options.rb +37 -32
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +3 -1
- data/lib/hecks/adapters/driving/mcp_scope.rb +8 -7
- data/lib/hecks/adapters/driving/newcomer.rb +6 -5
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +13 -12
- data/lib/hecks/adapters/driving/ruby/bulk_verbs.rb +127 -0
- data/lib/hecks/adapters/driving/ruby/{chapter.rb → declaration.rb} +27 -27
- data/lib/hecks/adapters/driving/ruby/query_rows.rb +27 -0
- data/lib/hecks/adapters/driving/ruby.rb +25 -24
- data/lib/hecks/adapters/driving/usage_cache.rb +14 -3
- data/lib/hecks/assembly.rb +101 -0
- data/lib/hecks/behaviors/dsl.rb +13 -3
- data/lib/hecks/behaviors/expectations/boot.rb +7 -6
- data/lib/hecks/behaviors/expectations/checks.rb +1 -1
- data/lib/hecks/behaviors/expectations/dispatching.rb +10 -9
- data/lib/hecks/behaviors/expectations/qualify.rb +9 -9
- data/lib/hecks/behaviors/expectations.rb +24 -10
- data/lib/hecks/behaviors/ir.rb +4 -2
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +31 -4
- data/lib/hecks/bench/environment.rb +6 -2
- data/lib/hecks/bench/postgres_probe.rb +3 -1
- data/lib/hecks/bench/report.rb +4 -1
- data/lib/hecks/bench/suite.rb +9 -3
- data/lib/hecks/bluebook/acknowledgements.rb +81 -0
- data/lib/hecks/bluebook/aggregate.rb +13 -4
- data/lib/hecks/bluebook/ask_resolution.rb +17 -17
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -5
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +11 -7
- data/lib/hecks/bluebook/assembly/marks.rb +10 -1
- data/lib/hecks/bluebook/assembly/scalars.rb +8 -2
- data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
- data/lib/hecks/bluebook/assembly.rb +10 -10
- data/lib/hecks/bluebook/behaviour/aggregate.rb +2 -1
- data/lib/hecks/bluebook/behaviour/attribute.rb +12 -0
- data/lib/hecks/bluebook/behaviour/command.rb +1 -1
- data/lib/hecks/bluebook/behaviour/{chapter.rb → declaration.rb} +17 -17
- data/lib/hecks/bluebook/behaviour/domain_port.rb +1 -1
- data/lib/hecks/bluebook/behaviour/entity.rb +1 -0
- data/lib/hecks/bluebook/behaviour/hecksagon.rb +8 -1
- data/lib/hecks/bluebook/behaviour/process_manager.rb +4 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +4 -1
- data/lib/hecks/bluebook/behaviour/traits.rb +13 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +17 -16
- data/lib/hecks/bluebook/capabilities/package_declared.rb +65 -0
- data/lib/hecks/bluebook/capabilities.rb +32 -132
- data/lib/hecks/bluebook/command.rb +22 -7
- data/lib/hecks/bluebook/context_map.rb +104 -0
- data/lib/hecks/bluebook/{chapter.rb → declaration.rb} +13 -13
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +28 -4
- data/lib/hecks/bluebook/dsl/aggregate_builder/draining.rb +2 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder/one_of_reference.rb +54 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/query_field_sealing.rb +11 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder/query_sealing.rb +15 -5
- data/lib/hecks/bluebook/dsl/aggregate_builder/relationships.rb +21 -3
- data/lib/hecks/bluebook/dsl/aggregate_builder/rules.rb +22 -22
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +2 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +27 -22
- data/lib/hecks/bluebook/dsl/bluebook_builder/pending_givens.rb +22 -22
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +27 -21
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +3 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/event_shapes.rb +5 -4
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +3 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/hop_tails.rb +13 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +4 -4
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +38 -84
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/query_hops.rb +5 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/references.rb +6 -6
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +6 -6
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +3 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +67 -67
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +48 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +28 -15
- data/lib/hecks/bluebook/dsl/context_map_builder.rb +73 -0
- data/lib/hecks/bluebook/dsl/entity_builder/rules.rb +17 -17
- data/lib/hecks/bluebook/dsl/entity_builder/scope.rb +3 -3
- data/lib/hecks/bluebook/dsl/entity_builder/sealing.rb +2 -2
- data/lib/hecks/bluebook/dsl/entity_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder/{chapter_scope.rb → bluebook_scope.rb} +14 -14
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +33 -27
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +6 -2
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +4 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +14 -8
- data/lib/hecks/bluebook/dsl/read_model_builder/sealing.rb +6 -11
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +1 -2
- data/lib/hecks/bluebook/dsl/word_gate.rb +2 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl.rb +3 -2
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +4 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +5 -3
- data/lib/hecks/bluebook/expression/ast_reader/blocks.rb +41 -0
- data/lib/hecks/bluebook/expression/ast_reader.rb +2 -10
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -4
- data/lib/hecks/bluebook/expression/evaluator/parser.rb +4 -1
- data/lib/hecks/bluebook/expression/evaluator.rb +13 -9
- data/lib/hecks/bluebook/expression/projection.json +6 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +50 -2
- data/lib/hecks/bluebook/expression/resolver/lookups.rb +2 -1
- data/lib/hecks/bluebook/expression/resolver/operations.rb +1 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +21 -15
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +4 -1
- data/lib/hecks/bluebook/expression.rb +1 -1
- data/lib/hecks/bluebook/hecksagon.rb +48 -23
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/judge/cells.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge/identity.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +27 -27
- data/lib/hecks/bluebook/meta_validator/plan.rb +5 -5
- data/lib/hecks/bluebook/meta_validator/readings/fields.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +11 -3
- data/lib/hecks/bluebook/meta_validator/reconstruction/reading.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +19 -19
- data/lib/hecks/bluebook/meta_validator/shapes.rb +6 -1
- data/lib/hecks/bluebook/meta_validator/syntax_boot/disk_cache.rb +18 -15
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +21 -15
- data/lib/hecks/bluebook/meta_validator/verdict_cache/prebuilt.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +11 -10
- data/lib/hecks/bluebook/meta_validator.rb +25 -22
- data/lib/hecks/bluebook/model_check/aggregate_checks.rb +68 -0
- data/lib/hecks/bluebook/model_check/ask_checks.rb +6 -5
- data/lib/hecks/bluebook/model_check/client_profile.rb +8 -42
- data/lib/hecks/bluebook/model_check/context_checks.rb +97 -0
- data/lib/hecks/bluebook/model_check/context_map_checks.rb +158 -0
- data/lib/hecks/bluebook/model_check/lifecycle_checks.rb +27 -5
- data/lib/hecks/bluebook/model_check/one_of_checks.rb +106 -0
- data/lib/hecks/bluebook/model_check/orphan_checks.rb +129 -0
- data/lib/hecks/bluebook/model_check/redundant_relationships.rb +38 -0
- data/lib/hecks/bluebook/model_check/reference_checks.rb +11 -1
- data/lib/hecks/bluebook/model_check/saga_checks.rb +8 -3
- data/lib/hecks/bluebook/model_check/verbs.rb +4 -4
- data/lib/hecks/bluebook/model_check.rb +32 -12
- data/lib/hecks/bluebook/named_pattern.rb +12 -1
- data/lib/hecks/bluebook/one_of_union.rb +35 -0
- data/lib/hecks/bluebook/pattern_subset.rb +12 -3
- data/lib/hecks/bluebook/project_loader.rb +3 -2
- data/lib/hecks/bluebook/project_register.rb +21 -5
- data/lib/hecks/bluebook/query.rb +4 -1
- data/lib/hecks/bluebook/read_model.rb +5 -5
- data/lib/hecks/bluebook/reference.rb +3 -3
- data/lib/hecks/bluebook/smoke_test.rb +15 -13
- data/lib/hecks/bluebook/synthesizer.rb +48 -13
- data/lib/hecks/bluebook/translation_expression/parser.rb +14 -5
- data/lib/hecks/bluebook.rb +6 -3
- data/lib/hecks/bulk/bluebook/adapters/row_sweeper.adapter +7 -0
- data/lib/hecks/bulk/bluebook/bulk.behaviors +18 -0
- data/lib/hecks/bulk/bluebook/bulk.bluebook +126 -0
- data/lib/hecks/bulk/bluebook/bulk.hecksagon +11 -0
- data/lib/hecks/bulk/bluebook/bulk.ports.hecksagon +13 -0
- data/lib/hecks/bulk/plan.rb +60 -0
- data/lib/hecks/bulk/row_sweeper.rb +34 -0
- data/lib/hecks/bulk/set_arguments.rb +68 -0
- data/lib/hecks/bulk/set_eligibility.rb +50 -0
- data/lib/hecks/bulk/set_planner.rb +100 -0
- data/lib/hecks/bulk/set_sweep.rb +78 -0
- data/lib/hecks/bulk/sweep.rb +67 -0
- data/lib/hecks/cli/check_era.rb +6 -6
- data/lib/hecks/cli/command_table.rb +2 -2
- data/lib/hecks/cli/console.rb +11 -6
- data/lib/hecks/cli/document.rb +14 -9
- data/lib/hecks/cli/domain_writer.rb +4 -1
- data/lib/hecks/cli/expected_era.rb +139 -0
- data/lib/hecks/cli/expression_projection.rb +2 -2
- data/lib/hecks/cli/follow.rb +3 -3
- data/lib/hecks/cli/history.rb +4 -1
- data/lib/hecks/cli/interview_draft.rb +8 -1
- data/lib/hecks/cli/interview_record.rb +7 -3
- data/lib/hecks/cli/interview_run.rb +7 -5
- data/lib/hecks/cli/interview_session/proposals.rb +5 -2
- data/lib/hecks/cli/interview_session.rb +3 -2
- data/lib/hecks/cli/interview_shape/lifecycle.rb +92 -0
- data/lib/hecks/cli/interview_shape.rb +19 -73
- data/lib/hecks/cli/model_check/examination.rb +63 -14
- data/lib/hecks/cli/model_check/sources.rb +4 -2
- data/lib/hecks/cli/model_check.rb +1 -1
- data/lib/hecks/cli/persistence_notice.rb +45 -0
- data/lib/hecks/cli/present.rb +1 -1
- data/lib/hecks/cli/project_cli/launcher_source.rb +12 -10
- data/lib/hecks/cli/project_cli/templates.rb +24 -3
- data/lib/hecks/cli/project_cli.rb +11 -13
- data/lib/hecks/cli/project_diagrams.rb +20 -20
- data/lib/hecks/cli/project_glossary.rb +14 -14
- data/lib/hecks/cli/reference.rb +1 -1
- data/lib/hecks/cli/refresh_projections.rb +16 -11
- data/lib/hecks/cli/run.rb +27 -5
- data/lib/hecks/cli/seed_semantics_corpus.rb +14 -4
- data/lib/hecks/cli/shape.rb +3 -4
- data/lib/hecks/cli/smoke_http/transport.rb +3 -2
- data/lib/hecks/cli/statements.rb +7 -7
- data/lib/hecks/cli/stores.rb +70 -2
- data/lib/hecks/cli/stress_concurrency_specs/reporting.rb +4 -1
- data/lib/hecks/codemod.rb +3 -3
- data/lib/hecks/composer/adapters/claude_composer.rb +3 -2
- data/lib/hecks/composer/agent.rb +1 -1
- data/lib/hecks/composer/assistant.rb +2 -2
- data/lib/hecks/composer/behavior_run.rb +7 -1
- data/lib/hecks/composer/bluebook/composer.bluebook +18 -25
- data/lib/hecks/composer/bluebook/composer.hecksagon +1 -1
- data/lib/hecks/composer/board.rb +6 -6
- data/lib/hecks/composer/board_edit/command_text.rb +4 -1
- data/lib/hecks/composer/board_edit/source.rb +18 -9
- data/lib/hecks/composer/board_edit.rb +2 -2
- data/lib/hecks/composer/check.rb +22 -22
- data/lib/hecks/composer/domains.rb +1 -1
- data/lib/hecks/composer/live.rb +19 -13
- data/lib/hecks/composer/outline.rb +4 -1
- data/lib/hecks/composer/page.html +1 -1
- data/lib/hecks/composer/panels.rb +6 -3
- data/lib/hecks/composer/parse.rb +53 -36
- data/lib/hecks/composer/session.rb +22 -16
- data/lib/hecks/composer/toolbox.rb +9 -6
- data/lib/hecks/composer/web.rb +3 -2
- data/lib/hecks/composer/workbench.rb +12 -9
- data/lib/hecks/composer.rb +1 -1
- data/lib/hecks/construct.rb +7 -7
- data/lib/hecks/corpus/adapters.rb +2 -1
- data/lib/hecks/corpus/routes.rb +25 -5
- data/lib/hecks/corpus/rust.rb +59 -24
- data/lib/hecks/corpus/sources.rb +12 -11
- data/lib/hecks/corpus/worlds.rb +2 -1
- data/lib/hecks/corpus.rb +30 -22
- data/lib/hecks/deploy/bluebook/deploy.bluebook +304 -518
- data/lib/hecks/doc/reference/harvester.rb +4 -1
- data/lib/hecks/doc/reference/readme.rb +11 -6
- data/lib/hecks/doc/reference/rendering.rb +11 -5
- data/lib/hecks/doc/reference.rb +7 -5
- data/lib/hecks/embryonaut_bluebook/lock.rb +5 -1
- data/lib/hecks/embryonaut_bluebook/manifest.rb +7 -1
- data/lib/hecks/embryonaut_bluebook/registry/changelog.rb +4 -1
- data/lib/hecks/embryonaut_bluebook/registry/versions.rb +13 -2
- data/lib/hecks/embryonaut_bluebook/registry.rb +5 -2
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -1
- data/lib/hecks/embryonaut_bluebook/vendor.rb +5 -2
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +15 -1
- data/lib/hecks/embryonaut_bluebook.rb +26 -12
- data/lib/hecks/engine_agreement.rb +4 -1
- data/lib/hecks/forms/app/commands.rb +35 -2
- data/lib/hecks/forms/app/records.rb +12 -12
- data/lib/hecks/forms/app/responses.rb +4 -2
- data/lib/hecks/forms/app.rb +22 -19
- data/lib/hecks/forms/command_form_renderer.rb +2 -2
- data/lib/hecks/forms/field_controls.rb +6 -1
- data/lib/hecks/forms/field_shape.rb +14 -6
- data/lib/hecks/forms/html.rb +8 -7
- data/lib/hecks/forms/html_pattern.rb +80 -0
- data/lib/hecks/forms/index_renderer.rb +11 -11
- data/lib/hecks/forms/params.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +4 -1
- data/lib/hecks/forms/query_form_renderer.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/reference_options.rb +2 -1
- data/lib/hecks/forms/text_shape.rb +4 -1
- data/lib/hecks/forms/value_object_shape.rb +1 -1
- data/lib/hecks/forms.rb +7 -7
- data/lib/hecks/fqn.rb +8 -2
- data/lib/hecks/framework/bluebook/change.bluebook +10 -15
- data/lib/hecks/framework/bluebook/change.hecksagon +1 -1
- data/lib/hecks/framework/bluebook/compliance.bluebook +27 -126
- data/lib/hecks/framework/bluebook/console_settings.bluebook +199 -114
- data/lib/hecks/framework/bluebook/framework.hecksagon +7 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +36 -14
- data/lib/hecks/framework/bluebook/identity.bluebook +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +123 -19
- data/lib/hecks/framework/bluebook/privacy.key_keeper.adapter +7 -0
- data/lib/hecks/framework/bluebook/privacy.ports.hecksagon +19 -0
- data/lib/hecks/framework/compliance.oidc.json +6 -36
- data/lib/hecks/framework/console_settings.oidc.json +39 -24
- data/lib/hecks/framework/privacy.oidc.json +25 -0
- data/lib/hecks/framework.rb +24 -8
- data/lib/hecks/framework_bluebooks.rb +156 -0
- data/lib/hecks/freezer.rb +7 -1
- data/lib/hecks/fuzzing/combination_miner.rb +9 -6
- data/lib/hecks/fuzzing/concurrent_dispatch/world_copy.rb +3 -1
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +2 -2
- data/lib/hecks/fuzzing/domain_generator.rb +8 -3
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/failure_triage.rb +13 -6
- data/lib/hecks/fuzzing/form_census/{chapter_forms.rb → bluebook_forms.rb} +12 -12
- data/lib/hecks/fuzzing/form_census.rb +14 -12
- data/lib/hecks/fuzzing/generated_domain_check.rb +2 -1
- data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +5 -5
- data/lib/hecks/fuzzing/isolated_boot/scratch_schema.rb +19 -9
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +13 -8
- data/lib/hecks/fuzzing/isolated_boot.rb +4 -5
- data/lib/hecks/fuzzing/mutation/checks.rb +2 -1
- data/lib/hecks/fuzzing/mutation/finders.rb +25 -8
- data/lib/hecks/fuzzing/mutation/observation.rb +3 -1
- data/lib/hecks/fuzzing/mutation/source_map.rb +3 -1
- data/lib/hecks/fuzzing/nondeterministic.rb +10 -4
- data/lib/hecks/fuzzing/outside_world.rb +1 -1
- data/lib/hecks/fuzzing/projection_agreement/binding.rb +63 -0
- data/lib/hecks/fuzzing/projection_agreement/comparison.rb +104 -0
- data/lib/hecks/fuzzing/projection_agreement.rb +68 -0
- data/lib/hecks/fuzzing/properties/aggregation_recompute.rb +1 -1
- data/lib/hecks/fuzzing/properties/bulk_parity.rb +118 -0
- data/lib/hecks/fuzzing/properties/catalog.rb +7 -0
- data/lib/hecks/fuzzing/properties/cursor_walks.rb +56 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +39 -1
- data/lib/hecks/fuzzing/properties/group_by_recompute.rb +6 -2
- data/lib/hecks/fuzzing/properties/grouped_reduction_recompute.rb +88 -0
- data/lib/hecks/fuzzing/properties/guards.rb +4 -1
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +12 -2
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +2 -1
- data/lib/hecks/fuzzing/properties/mutation_recompute.rb +3 -2
- data/lib/hecks/fuzzing/properties/piece_invariants.rb +3 -2
- data/lib/hecks/fuzzing/properties/query_rows.rb +5 -3
- data/lib/hecks/fuzzing/properties/querying.rb +6 -3
- data/lib/hecks/fuzzing/properties/read_model_names.rb +8 -2
- data/lib/hecks/fuzzing/properties/read_models.rb +2 -1
- data/lib/hecks/fuzzing/properties/refusals.rb +35 -0
- data/lib/hecks/fuzzing/properties/role_checks.rb +3 -2
- data/lib/hecks/fuzzing/properties/set_transform_parity.rb +73 -0
- data/lib/hecks/fuzzing/properties/tenant_scope.rb +3 -2
- data/lib/hecks/fuzzing/properties.rb +6 -0
- data/lib/hecks/fuzzing/qa_settings.rb +4 -4
- data/lib/hecks/fuzzing/replay/fan_out.rb +4 -3
- data/lib/hecks/fuzzing/replay/filters.rb +2 -2
- data/lib/hecks/fuzzing/replay/guard_check.rb +2 -2
- data/lib/hecks/fuzzing/replay/mutation_trace.rb +23 -10
- data/lib/hecks/fuzzing/replay/observation.rb +11 -7
- data/lib/hecks/fuzzing/replay/read_only_steps.rb +40 -3
- data/lib/hecks/fuzzing/replay/refusal_trace.rb +44 -0
- data/lib/hecks/fuzzing/replay/role_check.rb +2 -2
- data/lib/hecks/fuzzing/replay/session.rb +38 -18
- data/lib/hecks/fuzzing/replay/validation_reports.rb +61 -0
- data/lib/hecks/fuzzing/replay.rb +2 -2
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +3 -3
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +18 -8
- data/lib/hecks/fuzzing/self_consistency/sagas.rb +2 -2
- data/lib/hecks/fuzzing/self_consistency/value_objects.rb +9 -2
- data/lib/hecks/fuzzing/self_consistency.rb +4 -8
- data/lib/hecks/fuzzing/sequence_generator/adversary/argument_mutations.rb +15 -5
- data/lib/hecks/fuzzing/sequence_generator/adversary/caller_draw.rb +14 -10
- data/lib/hecks/fuzzing/sequence_generator/adversary/precedence_mutations.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator/argument_drawing.rb +2 -1
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +21 -13
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +3 -3
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -2
- data/lib/hecks/fuzzing/sequence_generator.rb +1 -2
- data/lib/hecks/fuzzing/shrinker.rb +4 -1
- data/lib/hecks/fuzzing/value_generator.rb +14 -8
- data/lib/hecks/fuzzing.rb +1 -0
- data/lib/hecks/grammar/dialects.rb +56 -0
- data/lib/hecks/grammar/evolve/arguments.rb +1 -1
- data/lib/hecks/grammar/evolve/keywords.rb +1 -1
- data/lib/hecks/grammar/evolve/seeds.rb +34 -8
- data/lib/hecks/grammar/expression.bluebook +3 -0
- data/lib/hecks/grammar/expression_operators.json +1071 -1
- data/lib/hecks/grammar/operators.rb +45 -22
- data/lib/hecks/grammar/translation.bluebook +2 -0
- data/lib/hecks/grammar/translation_ledger.rb +45 -0
- data/lib/hecks/grammar/translation_rules.json +646 -0
- data/lib/hecks/grammar.rb +27 -23
- data/lib/hecks/hecks/adapters/claude_agents.adapter +7 -0
- data/lib/hecks/hecks/adapters/claude_agents.rb +119 -0
- data/lib/hecks/hecks/adapters/claude_transcript.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/cache_clearing.rb +32 -0
- data/lib/hecks/hecks/adapters/codebase/cargo_tests.rb +50 -0
- data/lib/hecks/hecks/adapters/codebase/composing.rb +3 -2
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +17 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/declarations.rb +144 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +5 -4
- data/lib/hecks/hecks/adapters/codebase/language.rb +1 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +7 -1
- data/lib/hecks/hecks/adapters/codebase/previewing.rb +62 -0
- data/lib/hecks/hecks/adapters/codebase/promotion/gate.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/reading.rb +94 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +6 -2
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +5 -2
- data/lib/hecks/hecks/adapters/codebase/search.rb +91 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +37 -2
- data/lib/hecks/hecks/adapters/codebase/static_directory.rb +59 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +2 -2
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +30 -11
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +41 -3
- data/lib/hecks/hecks/adapters/console_capture.rb +27 -0
- data/lib/hecks/hecks/adapters/deploy_plans.rb +12 -3
- data/lib/hecks/hecks/adapters/deploy_toolchain/answers.rb +14 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/scripts.rb +6 -3
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +3 -3
- data/lib/hecks/hecks/adapters/desktop_alert/alert.rb +50 -0
- data/lib/hecks/hecks/adapters/desktop_alert.adapter +7 -0
- data/lib/hecks/hecks/adapters/desktop_alert.rb +99 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +4 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +19 -12
- data/lib/hecks/hecks/adapters/git.rb +5 -5
- data/lib/hecks/hecks/adapters/git_checkout.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_checkout.rb +99 -0
- data/lib/hecks/hecks/adapters/git_landing/merger.rb +85 -0
- data/lib/hecks/hecks/adapters/git_landing.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_landing.rb +103 -0
- data/lib/hecks/hecks/adapters/git_launcher.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_launcher.rb +82 -0
- data/lib/hecks/hecks/adapters/git_refs.rb +8 -4
- data/lib/hecks/hecks/adapters/git_train/bench.rb +76 -0
- data/lib/hecks/hecks/adapters/git_train/carriage.rb +116 -0
- data/lib/hecks/hecks/adapters/git_train/local_branch.rb +69 -0
- data/lib/hecks/hecks/adapters/git_train/reading.rb +26 -0
- data/lib/hecks/hecks/adapters/git_train/survey.rb +60 -0
- data/lib/hecks/hecks/adapters/git_train.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_train.rb +88 -0
- data/lib/hecks/hecks/adapters/git_worktrees.rb +81 -0
- data/lib/hecks/hecks/adapters/host_http.rb +5 -4
- data/lib/hecks/hecks/adapters/in_process_access.rb +3 -2
- data/lib/hecks/hecks/adapters/in_process_boot.rb +62 -35
- data/lib/hecks/hecks/adapters/in_process_operations.rb +30 -4
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +7 -7
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +14 -7
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +9 -7
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +17 -5
- data/lib/hecks/hecks/adapters/journal_store/readings/attestation.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/readings/scaffolding.rb +7 -4
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +2 -2
- data/lib/hecks/hecks/adapters/journal_store.rb +2 -2
- data/lib/hecks/hecks/adapters/ledger/chain.rb +63 -0
- data/lib/hecks/hecks/adapters/ledger/journal.rb +105 -0
- data/lib/hecks/hecks/adapters/ledger.adapter +7 -0
- data/lib/hecks/hecks/adapters/ledger.rb +89 -0
- data/lib/hecks/hecks/adapters/local_files.rb +15 -1
- data/lib/hecks/hecks/adapters/pg_admin.rb +7 -1
- data/lib/hecks/hecks/adapters/rails_app_files.rb +64 -0
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +4 -1
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +9 -9
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +4 -4
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +3 -3
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +4 -1
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +1 -1
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +34 -5
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +5 -3
- data/lib/hecks/hecks/adapters/settings_reading.adapter +7 -0
- data/lib/hecks/hecks/adapters/settings_reading.rb +142 -0
- data/lib/hecks/hecks/adapters/shell.rb +3 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +8 -8
- data/lib/hecks/hecks/adapters/world_clearing.rb +2 -1
- data/lib/hecks/hecks/agents.bluebook +246 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +554 -586
- data/lib/hecks/hecks/composer.behaviors +10 -10
- data/lib/hecks/hecks/context_map.hecksagon +7 -6
- data/lib/hecks/hecks/custodian.behaviors +96 -1
- data/lib/hecks/hecks/custodian.bluebook +553 -157
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +3 -4
- data/lib/hecks/hecks/hecks.behaviors +2 -2
- data/lib/hecks/hecks/hecks.bluebook +9 -14
- data/lib/hecks/hecks/hecks.contextmap +25 -0
- data/lib/hecks/hecks/hecks.hecksagon +139 -6
- data/lib/hecks/hecks/hecks.world +22 -21
- data/lib/hecks/hecks/ledger.bluebook +190 -0
- data/lib/hecks/hecks/rehearsal.bluebook +43 -77
- data/lib/hecks/hecks/shapes.json +17 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +3 -3
- data/lib/hecks/hecks/tooling.bluebook +170 -174
- data/lib/hecks/hecks/translations/2-148748.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-15a2e4.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-1b447c.bluebook +5 -0
- data/lib/hecks/hecks/translations/2-240474.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-44446d.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-46ca26.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-473eab.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-75cc28.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-781c07.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-84c18b.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-aa5fff.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-bfdb48.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-cd96d8.bluebook +10 -0
- data/lib/hecks/hecks/translations/2-d43f71.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-eea577.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-f53248.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-fbb677.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-ffdf12.bluebook +6 -0
- data/lib/hecks/ir.rb +8 -1
- data/lib/hecks/kernel_capabilities/templates.rb +4 -3
- data/lib/hecks/kernel_capabilities.rb +6 -6
- data/lib/hecks/language/adapter.bluebook +61 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +36 -6
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +1 -1
- data/lib/hecks/language/bluebook/bluebook.behaviors +6 -6
- data/lib/hecks/language/bluebook/bluebook.bluebook +28 -51
- data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
- data/lib/hecks/language/bluebook/command.bluebook +34 -2
- data/lib/hecks/language/bluebook/entity.bluebook +2 -2
- data/lib/hecks/language/bluebook/policy.behaviors +2 -2
- data/lib/hecks/language/bluebook/policy.bluebook +3 -3
- data/lib/hecks/language/bluebook/process_manager.bluebook +3 -3
- data/lib/hecks/language/bluebook/projection.bluebook +11 -10
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +1 -1
- data/lib/hecks/language/bluebook/syntax.bluebook +3 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +287 -6
- data/lib/hecks/language/context_map/context_map.bluebook +90 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +2 -2
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +8 -8
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +1 -1
- data/lib/hecks/language/sentence/sentence.bluebook +210 -0
- data/lib/hecks/language/world/world.bluebook +6 -6
- data/lib/hecks/literal.rb +8 -2
- data/lib/hecks/naming.rb +44 -14
- data/lib/hecks/persistence_legacy_fixture/postgres_stores.rb +7 -6
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +6 -2
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/agent/answers.rb +2 -2
- data/lib/hecks/ports/agent.rb +2 -2
- data/lib/hecks/ports/authentication.port +6 -0
- data/lib/hecks/ports/authentication.rb +67 -0
- data/lib/hecks/ports/authorization.rb +3 -3
- data/lib/hecks/ports/dispatch.port +5 -0
- data/lib/hecks/ports/dispatch.rb +28 -0
- data/lib/hecks/ports/key_vault.rb +9 -6
- data/lib/hecks/ports/loading.rb +23 -7
- data/lib/hecks/ports/persistence/append_only.rb +18 -1
- data/lib/hecks/ports/persistence/binding_policy.rb +70 -11
- data/lib/hecks/ports/persistence/codec_boundary.rb +3 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +10 -10
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +4 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +11 -11
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +6 -6
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +32 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +0 -1
- data/lib/hecks/ports/persistence/remote_runtime.rb +3 -2
- data/lib/hecks/ports/persistence/set_statement.rb +29 -0
- data/lib/hecks/ports/persistence/set_transforming.rb +30 -0
- data/lib/hecks/ports/persistence/state_codec.rb +3 -3
- data/lib/hecks/ports/persistence.rb +0 -4
- data/lib/hecks/ports/projection.rb +4 -1
- data/lib/hecks/ports/query/cursor.rb +131 -0
- data/lib/hecks/ports/query/in_memory.rb +17 -10
- data/lib/hecks/ports/query.rb +4 -2
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projection_files/expression_tables.rb +3 -3
- data/lib/hecks/projection_files/sentence_table.rb +17 -0
- data/lib/hecks/projection_files.rb +16 -13
- data/lib/hecks/projections/bootstrap_table.rb +3 -3
- data/lib/hecks/projections/command_schema/shape.rb +95 -0
- data/lib/hecks/projections/command_schema.rb +74 -0
- data/lib/hecks/projections/context_map_diagram.rb +135 -0
- data/lib/hecks/projections/context_relationships.rb +125 -0
- data/lib/hecks/projections/deploy/box/access.rb +7 -4
- data/lib/hecks/projections/deploy/box/caddy.rb +2 -1
- data/lib/hecks/projections/deploy/box/compose.rb +4 -8
- data/lib/hecks/projections/deploy/box/edge.rb +2 -1
- data/lib/hecks/projections/deploy/box/hosting.rb +4 -2
- data/lib/hecks/projections/deploy/box/hosting_parts.rb +27 -12
- data/lib/hecks/projections/deploy/box/logging.rb +2 -3
- data/lib/hecks/projections/deploy/box/roll_recipe.rb +1 -3
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +1 -16
- data/lib/hecks/projections/deploy/box/settings/containers.rb +29 -15
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +5 -6
- data/lib/hecks/projections/deploy/box/settings/types.rb +5 -10
- data/lib/hecks/projections/deploy/box/settings.rb +9 -8
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +10 -14
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +0 -1
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -3
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +7 -6
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +2 -6
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +2 -6
- data/lib/hecks/projections/deploy/box.rb +16 -9
- data/lib/hecks/projections/deploy/fargate/assembly.rb +7 -2
- data/lib/hecks/projections/deploy/fargate/cdn/options.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/check.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/containers/rendering.rb +8 -5
- data/lib/hecks/projections/deploy/fargate/containers/routing.rb +2 -1
- data/lib/hecks/projections/deploy/fargate/monitoring/reading.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/sections.rb +7 -4
- data/lib/hecks/projections/deploy/fargate/settings/ids.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +13 -4
- data/lib/hecks/projections/deploy/fargate/stack/blocks.rb +6 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +2 -1
- data/lib/hecks/projections/deploy/fargate/stack.rb +11 -6
- data/lib/hecks/projections/deploy/fargate/yaml.rb +10 -3
- data/lib/hecks/projections/deploy/fargate.rb +8 -3
- data/lib/hecks/projections/deploy/lambda/stack/blocks.rb +4 -2
- data/lib/hecks/projections/deploy/lambda/stack/deploy_chain.rb +2 -1
- data/lib/hecks/projections/deploy/lambda/stack/reading.rb +12 -9
- data/lib/hecks/projections/deploy/lambda/stack/reading_web.rb +7 -2
- data/lib/hecks/projections/deploy/lambda/stack/web.rb +2 -1
- data/lib/hecks/projections/deploy/lambda.rb +8 -3
- data/lib/hecks/projections/deploy/preview/containers.rb +14 -1
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +2 -2
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +8 -6
- data/lib/hecks/projections/deploy/preview/settings.rb +23 -5
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +1 -1
- data/lib/hecks/projections/deploy/preview/yaml_text.rb +2 -1
- data/lib/hecks/projections/deploy/scripts/settings.rb +5 -2
- data/lib/hecks/projections/deploy/shared.rb +12 -1
- data/lib/hecks/projections/deploy/shared_database_instance.rb +1 -1
- data/lib/hecks/projections/deploy/smoke.rb +3 -1
- data/lib/hecks/projections/deploy/template_diff/normalizer.rb +4 -1
- data/lib/hecks/projections/deploy/templates/lambda/mint_era_shared.tmpl +2 -2
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/deploy/vercel.rb +1 -1
- data/lib/hecks/projections/diagrams/dispatch.rb +5 -3
- data/lib/hecks/projections/diagrams/statecharts.rb +8 -3
- data/lib/hecks/projections/diagrams/structure.rb +9 -6
- data/lib/hecks/projections/diagrams.rb +9 -7
- data/lib/hecks/projections/glossary/entries.rb +4 -4
- data/lib/hecks/projections/glossary/html/page.rb +4 -1
- data/lib/hecks/projections/glossary/html/parser.rb +13 -4
- data/lib/hecks/projections/glossary/html/sections.rb +4 -1
- data/lib/hecks/projections/glossary/html.rb +10 -3
- data/lib/hecks/projections/glossary/markdown.rb +4 -3
- data/lib/hecks/projections/glossary/mermaid.rb +8 -5
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sensitivity.rb +11 -4
- data/lib/hecks/projections/glossary/sentences/actions.rb +5 -2
- data/lib/hecks/projections/glossary/sentences/queries.rb +2 -1
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +15 -6
- data/lib/hecks/projections/glossary/sentences.rb +6 -3
- data/lib/hecks/projections/glossary/slugs.rb +8 -1
- data/lib/hecks/projections/glossary.rb +3 -3
- data/lib/hecks/projections/model.rb +10 -6
- data/lib/hecks/projections/oidc.rb +19 -7
- data/lib/hecks/projections/parser_table.rb +10 -10
- data/lib/hecks/projections/phrase_table.rb +105 -0
- data/lib/hecks/projections/plain_english.rb +14 -4
- data/lib/hecks/projections/rails_app.rb +77 -0
- data/lib/hecks/projections/reference.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +1 -1
- data/lib/hecks/projections/rust_vocabulary/tables.rb +20 -0
- data/lib/hecks/projections/rust_vocabulary.rb +14 -17
- data/lib/hecks/projections/site/admin.rb +7 -7
- data/lib/hecks/projections/site/admin_cms.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/blocks.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/checks.rb +9 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/composition.rb +20 -18
- data/lib/hecks/projections/site/cms_editor/destructive.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/drafts.rb +6 -2
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/limits.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +3 -1
- data/lib/hecks/projections/site/cms_editor/pickers.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/preview.rb +10 -4
- data/lib/hecks/projections/site/cms_editor/relations.rb +3 -2
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +32 -25
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/schema.rb +31 -29
- data/lib/hecks/projections/site/cms_editor/setting.rb +34 -27
- data/lib/hecks/projections/site/cms_editor/targets.rb +9 -6
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +3 -3
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +2 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +8 -3
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +4 -4
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +14 -14
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +12 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +12 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +1 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +2 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +11 -11
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +3 -3
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +8 -2
- data/lib/hecks/projections/site/cms_editor.rb +19 -16
- data/lib/hecks/projections/site/edge/checks.rb +1 -1
- data/lib/hecks/projections/site/edge/function_checks.rb +1 -1
- data/lib/hecks/projections/site/edge/function_code.rb +4 -1
- data/lib/hecks/projections/site/edge/pattern.rb +4 -1
- data/lib/hecks/projections/site/edge/route_checks.rb +2 -2
- data/lib/hecks/projections/site/edge/rules.rb +1 -1
- data/lib/hecks/projections/site/edge.rb +14 -8
- data/lib/hecks/projections/site/live_edge.rb +2 -2
- data/lib/hecks/projections/site/payload/fields/definitions.rb +1 -1
- data/lib/hecks/projections/site/payload/lifecycle/text.rb +1 -1
- data/lib/hecks/projections/site/payload/model.rb +16 -12
- data/lib/hecks/projections/site/payload/specs.rb +4 -2
- data/lib/hecks/projections/site/payload_driver.rb +9 -8
- data/lib/hecks/projections/site/probe.rb +12 -3
- data/lib/hecks/projections/site/regions.rb +9 -1
- data/lib/hecks/projections/site/role_probe.rb +7 -7
- data/lib/hecks/projections/site/root_rows.rb +4 -4
- data/lib/hecks/projections/site/routes_ts/listings.rb +13 -6
- data/lib/hecks/projections/site/routes_ts.rb +6 -3
- data/lib/hecks/projections/site/site_admin.rb +1 -1
- data/lib/hecks/projections/site/site_cdn.rb +3 -2
- data/lib/hecks/projections/site/site_host.rb +1 -1
- data/lib/hecks/projections/site/site_root.rb +4 -4
- data/lib/hecks/projections/site/table/checks.rb +4 -1
- data/lib/hecks/projections/site/table/navigation_checks.rb +2 -2
- data/lib/hecks/projections/site/table/row_builder.rb +7 -7
- data/lib/hecks/projections/site/table/row_defaults.rb +1 -1
- data/lib/hecks/projections/site/table.rb +32 -26
- data/lib/hecks/projections/statements/actions.rb +4 -1
- data/lib/hecks/projections/statements/relationships.rb +15 -2
- data/lib/hecks/projections/statements.rb +2 -2
- data/lib/hecks/projections/vocabulary.rb +6 -6
- data/lib/hecks/projections/world_skeleton.rb +68 -0
- data/lib/hecks/projections.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +12 -9
- data/lib/hecks/projector/cli_projector/claims.rb +8 -8
- data/lib/hecks/projector/cli_projector/command_specs.rb +9 -5
- data/lib/hecks/projector/cli_projector/listing.rb +20 -8
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/query_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/usage.rb +12 -8
- data/lib/hecks/projector/cli_projector.rb +2 -2
- data/lib/hecks/projector/docs_projector/behaviour.rb +5 -4
- data/lib/hecks/projector/docs_projector/closing.rb +3 -3
- data/lib/hecks/projector/docs_projector.rb +12 -7
- data/lib/hecks/projector/exporter/adapters.rb +15 -2
- data/lib/hecks/projector/exporter/bindings.rb +39 -134
- data/lib/hecks/projector/exporter/plugins.rb +55 -0
- data/lib/hecks/projector/exporter.rb +2 -0
- data/lib/hecks/projector/narrate_projector/commands.rb +4 -3
- data/lib/hecks/projector/narrate_projector/reactions.rb +2 -2
- data/lib/hecks/projector/narrate_projector.rb +9 -8
- data/lib/hecks/projector/target.rb +8 -8
- data/lib/hecks/projector.rb +9 -9
- data/lib/hecks/quality_control/adapters/agent.rb +2 -1
- data/lib/hecks/quality_control/adapters/github_checks.adapter +1 -1
- data/lib/hecks/quality_control/adapters/github_issues.adapter +1 -1
- data/lib/hecks/quality_control/adapters/github_issues.rb +6 -2
- data/lib/hecks/quality_control/adapters/target_tools.rb +9 -3
- data/lib/hecks/quality_control/cli/child.rb +1 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty/verdict.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_generated_domains/evaluation.rb +12 -2
- data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_log_bug/proof.rb +16 -3
- data/lib/hecks/quality_control/cli/qa_mine_combinations/booting.rb +4 -2
- data/lib/hecks/quality_control/cli/qa_mutate.rb +161 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode/report.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +2 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_tick/output.rb +28 -26
- data/lib/hecks/quality_control/cli/qa_tick/preflight.rb +12 -3
- data/lib/hecks/quality_control/cli/qa_tick.rb +24 -8
- data/lib/hecks/quality_control/quality_control.bluebook +93 -92
- data/lib/hecks/quality_control/quality_control.hecksagon +3 -3
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +3 -2
- data/lib/hecks/query_ir/rules.rb +9 -9
- data/lib/hecks/query_ir/touchpoints.rb +5 -2
- data/lib/hecks/query_ir.rb +3 -3
- data/lib/hecks/query_specification/common/comparison.rb +1 -1
- data/lib/hecks/query_specification/common/dsl.rb +3 -4
- data/lib/hecks/query_specification/hop_path.rb +7 -4
- data/lib/hecks/rails_app/bluebook/rails_app.bluebook +60 -0
- data/lib/hecks/rails_app/bluebook/rails_app.hecksagon +4 -0
- data/lib/hecks/refused.rb +104 -0
- data/lib/hecks/release/approval.rb +9 -2
- data/lib/hecks/release/gem_pin.rb +7 -3
- data/lib/hecks/release/lane.rb +1 -1
- data/lib/hecks/release/runner/clean_tree.rb +4 -1
- data/lib/hecks/release/runner/console.rb +2 -1
- data/lib/hecks/router/aggregate_reader.rb +3 -4
- data/lib/hecks/router/namespace_installer.rb +31 -17
- data/lib/hecks/router.rb +2 -10
- data/lib/hecks/runtime/adapter_lookup.rb +30 -7
- data/lib/hecks/runtime/adapter_namespace.rb +21 -0
- data/lib/hecks/runtime/asking.rb +30 -0
- data/lib/hecks/runtime/caller.rb +22 -0
- data/lib/hecks/runtime/capability_graph.rb +2 -1
- data/lib/hecks/runtime/collecting.rb +86 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +13 -8
- data/lib/hecks/runtime/command_interpreter/delegation.rb +46 -17
- data/lib/hecks/runtime/command_interpreter/hydration.rb +17 -21
- data/lib/hecks/runtime/command_interpreter/list_mutations.rb +2 -1
- data/lib/hecks/runtime/command_interpreter/persisting.rb +13 -12
- data/lib/hecks/runtime/command_interpreter.rb +17 -10
- data/lib/hecks/runtime/command_rules/admissibility.rb +17 -10
- data/lib/hecks/runtime/command_rules/authorization.rb +8 -6
- data/lib/hecks/runtime/command_rules/guard_state.rb +10 -10
- data/lib/hecks/runtime/command_rules/invariants.rb +3 -1
- data/lib/hecks/runtime/command_rules/missing_reference.rb +25 -0
- data/lib/hecks/runtime/command_rules/one_of_references.rb +44 -0
- data/lib/hecks/runtime/command_rules/references.rb +14 -14
- data/lib/hecks/runtime/command_rules.rb +2 -0
- data/lib/hecks/runtime/declarations.rb +66 -0
- data/lib/hecks/runtime/dependency_planning/analyzer.rb +2 -1
- data/lib/hecks/runtime/dependency_planning/expression_reads.rb +3 -2
- data/lib/hecks/runtime/dependency_planning/mutation_analysis.rb +2 -1
- data/lib/hecks/runtime/dispatch_claim.rb +72 -0
- data/lib/hecks/runtime/dispatch_context.rb +41 -0
- data/lib/hecks/runtime/dispatcher/queries.rb +17 -0
- data/lib/hecks/runtime/dispatcher/routing.rb +34 -12
- data/lib/hecks/runtime/dispatcher/validating.rb +73 -0
- data/lib/hecks/runtime/dispatcher.rb +39 -23
- data/lib/hecks/runtime/entity_element/location.rb +15 -7
- data/lib/hecks/runtime/entity_element/mutations.rb +4 -1
- data/lib/hecks/runtime/entity_interpreter/enforcement.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter/locating.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter/saving.rb +5 -5
- data/lib/hecks/runtime/entity_interpreter.rb +11 -4
- data/lib/hecks/runtime/errors.rb +30 -17
- data/lib/hecks/runtime/identity.rb +9 -0
- data/lib/hecks/runtime/instance.rb +3 -2
- data/lib/hecks/runtime/interpreting.rb +63 -6
- data/lib/hecks/runtime/invocation_builder.rb +7 -4
- data/lib/hecks/runtime/invocation_facts.rb +13 -8
- data/lib/hecks/runtime/loader/boot_steps.rb +3 -2
- data/lib/hecks/runtime/loader/described.rb +4 -0
- data/lib/hecks/runtime/loader.rb +52 -62
- data/lib/hecks/runtime/outbox/redrive.rb +105 -0
- data/lib/hecks/runtime/outbox/relay.rb +11 -39
- data/lib/hecks/runtime/outbox/runnable.rb +60 -0
- data/lib/hecks/runtime/outbox.rb +15 -1
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -1
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +8 -35
- data/lib/hecks/runtime/policy_interpreter.rb +8 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +27 -13
- data/lib/hecks/runtime/query_interpreter/outside_answer.rb +1 -1
- data/lib/hecks/runtime/query_interpreter/paging.rb +42 -0
- data/lib/hecks/runtime/query_interpreter/plugin_answer.rb +54 -0
- data/lib/hecks/runtime/query_interpreter/store_specification.rb +32 -0
- data/lib/hecks/runtime/query_interpreter.rb +16 -9
- data/lib/hecks/runtime/reaction_invocation/target_resolution.rb +10 -4
- data/lib/hecks/runtime/reaction_invocation.rb +5 -2
- data/lib/hecks/runtime/read_model_interpreter/head_ordering.rb +1 -1
- data/lib/hecks/runtime/read_model_interpreter/reductions.rb +23 -15
- data/lib/hecks/runtime/read_model_interpreter/shaping.rb +18 -7
- data/lib/hecks/runtime/read_model_interpreter.rb +5 -2
- data/lib/hecks/runtime/reference_hop.rb +3 -2
- data/lib/hecks/runtime/refusal_wording.rb +15 -6
- data/lib/hecks/runtime/registry/adapter_capabilities.rb +43 -0
- data/lib/hecks/runtime/registry/ask_verification.rb +15 -4
- data/lib/hecks/runtime/registry/{chapter_verification.rb → bluebook_verification.rb} +23 -23
- data/lib/hecks/runtime/registry/context_maps.rb +30 -0
- data/lib/hecks/runtime/registry/declarations.rb +8 -6
- data/lib/hecks/runtime/registry/durability_warnings.rb +18 -2
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +27 -24
- data/lib/hecks/runtime/registry/plugin_query_verification.rb +45 -0
- data/lib/hecks/runtime/registry/providers.rb +34 -99
- data/lib/hecks/runtime/registry/query_verification.rb +30 -21
- data/lib/hecks/runtime/registry/runtime_state.rb +4 -3
- data/lib/hecks/runtime/registry/saga_persistence.rb +7 -16
- data/lib/hecks/runtime/registry/verification.rb +9 -5
- data/lib/hecks/runtime/registry/world_defaults.rb +8 -5
- data/lib/hecks/runtime/registry.rb +52 -27
- data/lib/hecks/runtime/row_sweep.rb +104 -0
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +5 -4
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +13 -10
- data/lib/hecks/runtime/saga_interpreter/redrive.rb +141 -0
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +35 -6
- data/lib/hecks/runtime/saga_interpreter.rb +36 -4
- data/lib/hecks/runtime/saga_pending_dispatch.rb +4 -2
- data/lib/hecks/runtime/scalar_field.rb +73 -0
- data/lib/hecks/runtime/tenant_scope.rb +2 -1
- data/lib/hecks/runtime/validation.rb +24 -0
- data/lib/hecks/runtime/value/admission.rb +11 -11
- data/lib/hecks/runtime/value/coercion.rb +7 -7
- data/lib/hecks/runtime/value/field_checks.rb +13 -13
- data/lib/hecks/runtime/value/invariant_violation.rb +4 -1
- data/lib/hecks/runtime/value/references.rb +7 -4
- data/lib/hecks/runtime/value/unwrapping.rb +60 -0
- data/lib/hecks/runtime/value/validation.rb +7 -4
- data/lib/hecks/runtime/value.rb +3 -27
- data/lib/hecks/runtime/wasm_plugin.rb +98 -0
- data/lib/hecks/runtime.rb +14 -14
- data/lib/hecks/rust_build/command_schemas.rb +27 -0
- data/lib/hecks/rust_build/conformance/surface.rb +50 -7
- data/lib/hecks/rust_build/conformance.rb +25 -3
- data/lib/hecks/rust_build/conformance_fuzz.rb +2 -2
- data/lib/hecks/rust_build/domain_name.rb +4 -1
- data/lib/hecks/rust_build/host.rb +15 -9
- data/lib/hecks/rust_build/native_build.rb +2 -1
- data/lib/hecks/rust_build/prebuilt_binary.rb +25 -5
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +11 -10
- data/lib/hecks/rust_build/project_rust/domain_loader.rb +15 -2
- data/lib/hecks/rust_build/project_rust/root_mod/notes.rb +1 -1
- data/lib/hecks/rust_build/project_rust/target_ir.rb +12 -3
- data/lib/hecks/rust_build/project_rust.rb +39 -19
- data/lib/hecks/rust_build/target_dir.rb +40 -0
- data/lib/hecks/rust_build/wasm.rb +46 -17
- data/lib/hecks/rust_build/wasm_browser.rb +8 -2
- data/lib/hecks/rust_build.rb +29 -2
- data/lib/hecks/sentence_form.rb +212 -0
- data/lib/hecks/served_options.rb +2 -1
- data/lib/hecks/shape_ledger.rb +149 -0
- data/lib/hecks/site/bluebook/site.bluebook +39 -62
- data/lib/hecks/site/bluebook/site.ports.hecksagon +1 -1
- data/lib/hecks/sme/bluebook/sme.bluebook +7 -1
- data/lib/hecks/storehouse/audit.rb +4 -1
- data/lib/hecks/storehouse/bluebook_words.rb +35 -0
- data/lib/hecks/storehouse/dispatching.rb +6 -3
- data/lib/hecks/storehouse/introspection.rb +4 -4
- data/lib/hecks/storehouse/querying.rb +2 -2
- data/lib/hecks/storehouse.rb +14 -4
- data/lib/hecks/switches.rb +21 -0
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +5 -9
- data/lib/hecks/text.rb +57 -0
- data/lib/hecks/tickets/bluebook/tickets.bluebook +30 -51
- data/lib/hecks/tickets/bluebook/tickets.hecksagon +1 -1
- data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +1 -1
- data/lib/hecks/tools/argument_gate_matrix/dispatching.rb +1 -1
- data/lib/hecks/tools/argument_gate_matrix/rows.rb +7 -7
- data/lib/hecks/tools/argument_gate_matrix/violations.rb +1 -1
- data/lib/hecks/tools/argument_gate_matrix.rb +3 -3
- data/lib/hecks/tools/ci_gate_decision.rb +5 -2
- data/lib/hecks/tools/ci_gates.rb +1 -1
- data/lib/hecks/tools/comment_style/caps_words.rb +11 -8
- data/lib/hecks/tools/comment_style/dictionary.rb +7 -2
- data/lib/hecks/tools/comment_style/fixing.rb +10 -5
- data/lib/hecks/tools/comment_style/long_blocks.rb +1 -1
- data/lib/hecks/tools/comment_style/masking.rb +5 -5
- data/lib/hecks/tools/comment_style/run.rb +4 -1
- data/lib/hecks/tools/comment_style/source_file.rb +10 -3
- data/lib/hecks/tools/comment_style/structure_checks.rb +13 -4
- data/lib/hecks/tools/comment_style/text_checks.rb +1 -1
- data/lib/hecks/tools/comment_style.rb +25 -2
- data/lib/hecks/tools/deploy_recipe/generation.rb +1 -1
- data/lib/hecks/tools/deploy_recipe/targets.rb +1 -1
- data/lib/hecks/tools/deploy_recipe/worlds.rb +15 -9
- data/lib/hecks/tools/deploy_recipe.rb +17 -10
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +1 -1
- data/lib/hecks/tools/deploy_recipe_lint/makefile.rb +7 -1
- data/lib/hecks/tools/drop_implicit_append_fields.rb +4 -1
- data/lib/hecks/tools/era_projection_backfill.rb +11 -6
- data/lib/hecks/tools/era_reattest.rb +11 -6
- data/lib/hecks/tools/fuzz_sweep/findings.rb +18 -2
- data/lib/hecks/tools/fuzz_sweep/options.rb +5 -1
- data/lib/hecks/tools/fuzz_sweep.rb +17 -5
- data/lib/hecks/tools/gate.rb +27 -5
- data/lib/hecks/tools/hoist_local_givens/rewriting.rb +3 -2
- data/lib/hecks/tools/hoist_local_givens.rb +2 -4
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +1 -1
- data/lib/hecks/tools/lanes/rows.rb +9 -3
- data/lib/hecks/tools/lanes/ruleset.rb +1 -1
- data/lib/hecks/tools/lanes/watch_workflow.rb +1 -1
- data/lib/hecks/tools/lanes/workflow.rb +1 -1
- data/lib/hecks/tools/lanes.rb +1 -1
- data/lib/hecks/tools/oidc_manifests.rb +16 -19
- data/lib/hecks/tools/regeneration_run.rb +22 -17
- data/lib/hecks/tools/release_only.rb +5 -2
- data/lib/hecks/tools/rust_comment_style/caps_words.rb +5 -5
- data/lib/hecks/tools/rust_comment_style/fixing.rb +1 -1
- data/lib/hecks/tools/rust_comment_style/masking.rb +7 -4
- data/lib/hecks/tools/rust_comment_style/source_file.rb +7 -2
- data/lib/hecks/tools/rust_comment_style/text_checks.rb +1 -1
- data/lib/hecks/tools/rust_comment_style/tokenizer.rb +4 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +16 -13
- data/lib/hecks/tools/site_routes/root_files.rb +14 -13
- data/lib/hecks/tools/site_routes/templates.rb +5 -4
- data/lib/hecks/tools/site_routes.rb +18 -15
- data/lib/hecks/tools/tail_merge.rb +3 -3
- data/lib/hecks/tools/tenant_provisioning.rb +6 -6
- data/lib/hecks/tools/tools_doc.rb +16 -16
- data/lib/hecks/tools/translation_audit/domain_loading.rb +3 -3
- data/lib/hecks/tools/translation_audit.rb +15 -4
- data/lib/hecks/tools/translation_scaffold/reporting.rb +4 -1
- data/lib/hecks/tools/translation_scaffold.rb +4 -14
- data/lib/hecks/vendoring/git_source.rb +7 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +110 -3
- data/lib/hecks.rb +37 -18
- data/lib/rubocop/cop/hecks/inline_regexp.rb +29 -0
- data/lib/rubocop/cop/hecks/no_transport_in_runtime.rb +78 -0
- data/lib/rubocop/cop/hecks/rails_model_rules.rb +46 -0
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
- data/lib/rubocop/cop/hecks/train_wreck.rb +69 -0
- data/lib/rubocop/cop/hecks/unnamed_pipeline.rb +51 -0
- data/prebuilt/31cf81e12f16236ad26249be590cfea3799c9db67432e6a4ef54b801c355b6ae.marshal +0 -0
- data/prebuilt/68ecb2bd3abb1a221007db4f2b0375834c13aaccac8f2b99778e8517f11aa113.marshal +0 -0
- data/prebuilt/verdicts-0328c2403383837d780ad5ed81d1da351c56436b894cf9015b5d56b8f1f1bf10.json +1 -0
- data/qa/settings.yml +9 -0
- data/rust/Cargo.lock +8 -1
- data/rust/Cargo.toml +6 -7
- data/rust/build/src/binding_pass.rs +13 -17
- data/rust/build/src/cargo_sync.rs +1 -1
- data/rust/build/src/lineage_pass.rs +4 -71
- data/rust/build/src/pipeline.rs +99 -70
- data/rust/build/src/resolve.rs +85 -29
- data/rust/build/src/seams_pass.rs +117 -99
- data/rust/build/src/world_pass.rs +21 -12
- data/rust/codegen/src/domain_generator.rs +30 -2
- data/rust/codegen/src/expr_emitter.rs +6 -0
- data/rust/codegen/src/json_codec.rs +48 -10
- data/rust/codegen/src/main.rs +41 -34
- data/rust/codegen/src/manifest.rs +1 -1
- data/rust/codegen/src/naming.rs +5 -5
- data/rust/codegen/src/one_of_references.rs +124 -0
- data/rust/codegen/src/presenters.rs +335 -0
- data/rust/codegen/src/queries.rs +56 -7
- data/rust/codegen/src/reactions.rs +19 -14
- data/rust/codegen/src/read_models.rs +20 -71
- data/rust/codegen/src/registry.rs +89 -15
- data/rust/codegen/src/sidecars.rs +1 -1
- data/rust/expr/Cargo.lock +54 -0
- data/rust/expr/Cargo.toml +52 -0
- data/rust/{src/kernel → expr/src}/attribute_shapes/composite.rs +45 -14
- data/rust/{src/kernel → expr/src}/attribute_shapes/list.rs +1 -1
- data/rust/{src/kernel → expr/src}/attribute_shapes/mod.rs +2 -2
- data/rust/{src/kernel → expr/src}/attribute_shapes/optional.rs +1 -1
- data/rust/{src/kernel → expr/src}/attribute_shapes/scalar.rs +3 -3
- data/rust/{src/kernel → expr/src}/expr.rs +30 -8
- data/rust/{src/kernel → expr/src}/expression_operators/arithmetic.rs +41 -18
- data/rust/{src/kernel → expr/src}/expression_operators/comparison.rs +54 -8
- data/rust/{src/kernel → expr/src}/expression_operators/enumeration.rs +73 -6
- data/rust/{src/kernel → expr/src}/expression_operators/logical.rs +2 -2
- data/rust/expr/src/expression_operators/membership.rs +33 -0
- data/rust/{src/kernel → expr/src}/expression_operators/mod.rs +2 -2
- data/rust/expr/src/expression_operators/pattern_match.rs +179 -0
- data/rust/{src/kernel → expr/src}/expression_operators/positional.rs +8 -6
- data/rust/{src/kernel → expr/src}/expression_operators/presence.rs +7 -6
- data/rust/{src/kernel → expr/src}/expression_operators/sign_test.rs +14 -4
- data/rust/{src/kernel → expr/src}/expression_operators/sized.rs +16 -9
- data/rust/{src/kernel → expr/src}/expression_operators/text.rs +30 -17
- data/rust/{src/kernel → expr/src}/expression_operators/to_string.rs +8 -6
- data/rust/expr/src/lib.rs +17 -0
- data/rust/expr/src/refusal.rs +62 -0
- data/rust/expr/src/rendering.rs +135 -0
- data/rust/{src/kernel → expr/src}/ruby_float.rs +7 -6
- data/rust/host/Cargo.lock +137 -123
- data/rust/host/Cargo.toml +7 -3
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/core/Cargo.toml +64 -0
- data/rust/host/core/clippy.toml +4 -0
- data/rust/host/core/src/arg_limits.rs +48 -0
- data/rust/host/core/src/clock.rs +146 -0
- data/rust/host/core/src/dispatch.rs +920 -0
- data/rust/host/core/src/expr_json.rs +1137 -0
- data/rust/host/{src → core/src}/host.rs +13 -14
- data/rust/host/{src → core/src}/ir.rs +227 -18
- data/rust/host/core/src/journal.rs +126 -0
- data/rust/host/{src → core/src}/lambda_client.rs +27 -96
- data/rust/host/core/src/lib.rs +37 -0
- data/rust/host/{src → core/src}/needs.rs +66 -20
- data/rust/host/core/src/plugins.rs +269 -0
- data/rust/host/core/src/presentation.rs +471 -0
- data/rust/host/{src → core/src}/presentation_write.rs +75 -43
- data/rust/host/{src → core/src}/query_step.rs +54 -16
- data/rust/host/{src → core/src}/reference_validate.rs +6 -6
- data/rust/host/{src → core/src}/storage_shape.rs +2 -2
- data/rust/host/core/src/store/memory.rs +635 -0
- data/rust/host/core/src/store.rs +418 -0
- data/rust/host/{src → core/src}/ui_schema.rs +8 -8
- data/rust/host/core/src/validation.rs +129 -0
- data/rust/host/core/src/wasm_runner.rs +410 -0
- data/rust/host/fuzz/.gitignore +4 -0
- data/rust/host/fuzz/Cargo.lock +4479 -0
- data/rust/host/fuzz/Cargo.toml +42 -0
- data/rust/host/fuzz/README.md +55 -0
- data/rust/host/fuzz/fuzz_targets/postgres_equivalence.rs +24 -0
- data/rust/host/fuzz/src/backend.rs +273 -0
- data/rust/host/fuzz/src/equivalence.rs +195 -0
- data/rust/host/fuzz/src/footprint.rs +182 -0
- data/rust/host/fuzz/src/lib.rs +17 -0
- data/rust/host/fuzz/src/ops.rs +345 -0
- data/rust/host/fuzz/tests/smoke.rs +50 -0
- data/rust/host/src/api.rs +316 -105
- data/rust/host/src/auth/google_signin.rs +183 -0
- data/rust/host/src/auth.rs +527 -370
- data/rust/host/src/aws_lambda_invoker.rs +61 -0
- data/rust/host/src/bin/expr_harness.rs +9 -4
- data/rust/host/src/bin/ledger.rs +84 -0
- data/rust/host/src/bin/lineage_harness.rs +17 -21
- data/rust/host/src/bin/mint_harness.rs +11 -36
- data/rust/host/src/bin/validate_harness.rs +1 -6
- data/rust/host/src/boot.rs +465 -174
- data/rust/host/src/boundary_fuzz/ir.rs +10 -5
- data/rust/host/src/boundary_fuzz/server.rs +6 -5
- data/rust/host/src/checkout.rs +4 -1
- data/rust/host/src/commerce_ir.rs +126 -105
- data/rust/host/src/{dispatch.rs → dispatch_tests.rs} +391 -824
- data/rust/host/src/extension.rs +3 -4
- data/rust/host/src/hecksagon_boundary_tests.rs +149 -0
- data/rust/host/src/ledger.rs +683 -0
- data/rust/host/src/lib.rs +21 -28
- data/rust/host/src/payments/tests.rs +136 -38
- data/rust/host/src/payments.rs +36 -32
- data/rust/host/src/platform_signin/extension.rs +1 -1
- data/rust/host/src/platform_signin/mapping_tests.rs +12 -9
- data/rust/host/src/platform_signin/session.rs +7 -14
- data/rust/host/src/platform_signin/verify.rs +0 -2
- data/rust/host/src/presentation_tests.rs +382 -0
- data/rust/host/src/rate_limit.rs +18 -2
- data/rust/host/src/server.rs +287 -36
- data/rust/host/src/{journal.rs → store/journal_sql.rs} +21 -127
- data/rust/host/src/{mint.rs → store/mint.rs} +12 -12
- data/rust/host/src/store/postgres.rs +493 -0
- data/rust/host/src/store.rs +97 -0
- data/rust/host/src/store_conformance.rs +1265 -0
- data/rust/host/src/web/commerce.rs +10 -18
- data/rust/host/src/web/newsletter/cooldown.rs +107 -22
- data/rust/host/src/web/newsletter/naming.rs +104 -0
- data/rust/host/src/web/newsletter/tests.rs +53 -0
- data/rust/host/src/web/newsletter.rs +73 -50
- data/rust/host/src/web/newsletter_send.rs +12 -13
- data/rust/host/src/web/registration_receipt.rs +2 -2
- data/rust/host/src/web/registrations.rs +754 -375
- data/rust/host/src/web/resend_webhook.rs +3 -3
- data/rust/host/src/web/signin_tests.rs +131 -0
- data/rust/host/src/web.rs +1479 -149
- data/rust/lsp/README.md +27 -27
- data/rust/lsp/src/diagnostics.rs +75 -15
- data/rust/lsp/src/main.rs +101 -5
- data/rust/parser/fuzz/Cargo.toml +2 -2
- data/rust/parser/fuzz/README.md +3 -3
- data/rust/parser/fuzz/fuzz_targets/{chapter.rs → bluebook.rs} +4 -4
- data/rust/parser/fuzz/fuzz_targets/resolve.rs +2 -2
- data/rust/parser/src/build/named_pattern.rs +22 -0
- data/rust/parser/src/build/references.rs +52 -0
- data/rust/parser/src/diag.rs +22 -1
- data/rust/parser/src/emit.rs +68 -4
- data/rust/parser/src/expr/ast_json.rs +10 -0
- data/rust/parser/src/expr/resolver.rs +49 -9
- data/rust/parser/src/ir.rs +34 -7
- data/rust/parser/src/keywords.rs +454 -457
- data/rust/parser/src/lex.rs +115 -87
- data/rust/parser/src/main.rs +180 -32
- data/rust/parser/src/parse/adapter.rs +28 -0
- data/rust/parser/src/parse/aggregate.rs +55 -35
- data/rust/parser/src/parse/command.rs +100 -15
- data/rust/parser/src/parse/{chapter.rs → declaration.rs} +85 -54
- data/rust/parser/src/parse/domain_port.rs +60 -9
- data/rust/parser/src/parse/entity.rs +35 -32
- data/rust/parser/src/parse/file.rs +1 -1
- data/rust/parser/src/parse/hecksagon.rs +15 -5
- data/rust/parser/src/parse/mod.rs +84 -2
- data/rust/parser/src/parse/process_manager.rs +1 -1
- data/rust/parser/src/parse/query.rs +13 -3
- data/rust/parser/src/parse/read_model.rs +2 -1
- data/rust/parser/src/phrases.rs +1258 -0
- data/rust/parser/src/render_ruby.rs +540 -0
- data/rust/parser/src/sentence/changes.rs +437 -0
- data/rust/parser/src/sentence/command_step.rs +206 -0
- data/rust/parser/src/sentence/complete.rs +276 -0
- data/rust/parser/src/sentence/entity.rs +41 -0
- data/rust/parser/src/sentence/examples.rs +1012 -0
- data/rust/parser/src/sentence/format.rs +882 -0
- data/rust/parser/src/sentence/lower.rs +1366 -0
- data/rust/parser/src/sentence/mod.rs +444 -0
- data/rust/parser/src/sentence/names.rs +69 -0
- data/rust/parser/src/sentence/options.rs +154 -0
- data/rust/parser/src/sentence/pattern.rs +433 -0
- data/rust/parser/src/sentence/process.rs +196 -0
- data/rust/parser/src/sentence/queries.rs +431 -0
- data/rust/parser/src/sentence/report.rs +283 -0
- data/rust/parser/src/sentence/token.rs +106 -0
- data/rust/parser/src/sentence/values.rs +498 -0
- data/rust/src/exemplar/queries.rs +1 -0
- data/rust/src/exemplar/reactions.rs +1 -1
- data/rust/src/exemplar/registry.rs +30 -0
- data/rust/src/kernel/cli.rs +233 -20
- data/rust/src/kernel/collecting.rs +174 -0
- data/rust/src/kernel/dispatch.rs +55 -27
- data/rust/src/kernel/json.rs +15 -1
- data/rust/src/kernel/mod.rs +8 -67
- data/rust/src/kernel/named_query.rs +225 -11
- data/rust/src/kernel/orchestrate.rs +213 -25
- data/rust/src/kernel/orchestrate_guard_tests.rs +3 -0
- data/rust/src/kernel/present.rs +261 -0
- data/rust/src/kernel/query_cursor.rs +251 -0
- data/rust/src/kernel/query_ordering.rs +6 -6
- data/rust/src/kernel/read_model.rs +162 -17
- data/rust/src/kernel/repository.rs +107 -17
- data/rust/src/kernel/vocab/mod.rs +4 -0
- data/rust/src/kernel/vocab/reducer.rs +109 -0
- data/rust/src/kernel/vocab/refusal_class.rs +124 -0
- data/rust/src/kernel/vocab/refusal_template.rs +2 -2
- data/rust/src/lib.rs +1 -1
- data/rust/web/Cargo.lock +47 -0
- metadata +331 -93
- data/lib/hecks/chapters.rb +0 -147
- data/lib/hecks/gate/stages.yml +0 -234
- data/lib/hecks/hecks/governance.world +0 -7
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +0 -137
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +0 -59
- data/lib/hecks/ports/persistence/postgres_dump.rb +0 -100
- data/lib/hecks/projections/deploy/box/templates/tunnel-egress.yaml.tmpl +0 -11
- data/lib/hecks/projector/exporter/capability_values.rb +0 -55
- data/lib/hecks/runtime/remote_dispatcher.rb +0 -176
- data/prebuilt/02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal +0 -0
- data/prebuilt/bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal +0 -0
- data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +0 -1
- data/rust/host/src/clock.rs +0 -8
- data/rust/host/src/expr_json.rs +0 -1983
- data/rust/host/src/presentation.rs +0 -842
- data/rust/host/src/wasm_runner.rs +0 -206
- data/rust/src/kernel/expression_operators/membership.rs +0 -20
- data/rust/src/kernel/expression_operators/pattern_match.rs +0 -108
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/events.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/head_writes.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/journal.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/base_sql.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/field_cache.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/head_compiler.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/merge_writes.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/provisioning.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/resumable_backfill.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_installer.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_sql.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/sagas.rb +0 -0
- /data/rust/host/{src → core/src}/field_hints.rs +0 -0
- /data/rust/host/{src → core/src}/log.rs +0 -0
- /data/rust/host/{src → core/src}/reference_transform.rs +0 -0
data/rust/host/src/expr_json.rs
DELETED
|
@@ -1,1983 +0,0 @@
|
|
|
1
|
-
//! Host-local JSON port of `rust::kernel::expr::Expr`: `parse` reads every node and `interpret`
|
|
2
|
-
//! evaluates every one with the Ruby runtime's semantics (`Evaluator`/`Resolver`): the same
|
|
3
|
-
//! answers, null handling and error wording, so a value object's invariants mint the same way.
|
|
4
|
-
|
|
5
|
-
use serde_json::Value as Json;
|
|
6
|
-
|
|
7
|
-
/// The `cmp` flags of a comparison node: `<`, `==`, and whether the result is negated.
|
|
8
|
-
#[derive(Debug, Clone, Copy)]
|
|
9
|
-
pub struct Comparison {
|
|
10
|
-
pub less_than: bool,
|
|
11
|
-
pub equal: bool,
|
|
12
|
-
pub negated: bool,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/// Which ends of a string `Expr::Strip` trims.
|
|
16
|
-
#[derive(Debug, Clone, Copy)]
|
|
17
|
-
pub enum StripSide {
|
|
18
|
-
Both,
|
|
19
|
-
Left,
|
|
20
|
-
Right,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
#[derive(Debug, Clone, Copy)]
|
|
24
|
-
pub enum BlockMode {
|
|
25
|
-
All,
|
|
26
|
-
Any,
|
|
27
|
-
None,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/// One JSON object per node, tagged by `"op"`; field names match the Hash keys
|
|
31
|
-
/// `ast_json.rb` emits.
|
|
32
|
-
#[derive(Debug, Clone)]
|
|
33
|
-
#[allow(dead_code)]
|
|
34
|
-
pub enum Expr {
|
|
35
|
-
Or {
|
|
36
|
-
left: Box<Self>,
|
|
37
|
-
right: Box<Self>,
|
|
38
|
-
},
|
|
39
|
-
And {
|
|
40
|
-
left: Box<Self>,
|
|
41
|
-
right: Box<Self>,
|
|
42
|
-
},
|
|
43
|
-
Not {
|
|
44
|
-
expr: Box<Self>,
|
|
45
|
-
},
|
|
46
|
-
Compare {
|
|
47
|
-
cmp: Comparison,
|
|
48
|
-
left: Box<Self>,
|
|
49
|
-
right: Box<Self>,
|
|
50
|
-
},
|
|
51
|
-
Include {
|
|
52
|
-
haystack: Box<Self>,
|
|
53
|
-
needle: Box<Self>,
|
|
54
|
-
},
|
|
55
|
-
Int {
|
|
56
|
-
value: i64,
|
|
57
|
-
},
|
|
58
|
-
Float {
|
|
59
|
-
value: f64,
|
|
60
|
-
},
|
|
61
|
-
Str {
|
|
62
|
-
value: String,
|
|
63
|
-
},
|
|
64
|
-
Bool {
|
|
65
|
-
value: bool,
|
|
66
|
-
},
|
|
67
|
-
Nil,
|
|
68
|
-
Add {
|
|
69
|
-
left: Box<Self>,
|
|
70
|
-
right: Box<Self>,
|
|
71
|
-
},
|
|
72
|
-
Sub {
|
|
73
|
-
left: Box<Self>,
|
|
74
|
-
right: Box<Self>,
|
|
75
|
-
},
|
|
76
|
-
Mul {
|
|
77
|
-
left: Box<Self>,
|
|
78
|
-
right: Box<Self>,
|
|
79
|
-
},
|
|
80
|
-
Div {
|
|
81
|
-
left: Box<Self>,
|
|
82
|
-
right: Box<Self>,
|
|
83
|
-
},
|
|
84
|
-
SignTest {
|
|
85
|
-
cmp: Comparison,
|
|
86
|
-
receiver: Box<Self>,
|
|
87
|
-
},
|
|
88
|
-
Empty {
|
|
89
|
-
receiver: Box<Self>,
|
|
90
|
-
},
|
|
91
|
-
ToS {
|
|
92
|
-
receiver: Box<Self>,
|
|
93
|
-
},
|
|
94
|
-
Modulo {
|
|
95
|
-
receiver: Box<Self>,
|
|
96
|
-
divisor: Box<Self>,
|
|
97
|
-
},
|
|
98
|
-
Size {
|
|
99
|
-
receiver: Box<Self>,
|
|
100
|
-
},
|
|
101
|
-
Lookup {
|
|
102
|
-
path: Vec<String>,
|
|
103
|
-
},
|
|
104
|
-
BlockPredicate {
|
|
105
|
-
mode: BlockMode,
|
|
106
|
-
receiver: Box<Self>,
|
|
107
|
-
param: String,
|
|
108
|
-
predicate: Box<Self>,
|
|
109
|
-
},
|
|
110
|
-
Find {
|
|
111
|
-
receiver: Box<Self>,
|
|
112
|
-
param: String,
|
|
113
|
-
predicate: Box<Self>,
|
|
114
|
-
path: Vec<String>,
|
|
115
|
-
},
|
|
116
|
-
Array {
|
|
117
|
-
elements: Vec<Self>,
|
|
118
|
-
},
|
|
119
|
-
MatchesRegex {
|
|
120
|
-
receiver: Box<Self>,
|
|
121
|
-
pattern: String,
|
|
122
|
-
flags: String,
|
|
123
|
-
},
|
|
124
|
-
Presence {
|
|
125
|
-
receiver: Box<Self>,
|
|
126
|
-
negated: bool,
|
|
127
|
-
},
|
|
128
|
-
Assignment {
|
|
129
|
-
receiver: Box<Self>,
|
|
130
|
-
negated: bool,
|
|
131
|
-
},
|
|
132
|
-
Split {
|
|
133
|
-
receiver: Box<Self>,
|
|
134
|
-
separator: String,
|
|
135
|
-
},
|
|
136
|
-
Strip {
|
|
137
|
-
receiver: Box<Self>,
|
|
138
|
-
side: StripSide,
|
|
139
|
-
},
|
|
140
|
-
StartsWith {
|
|
141
|
-
receiver: Box<Self>,
|
|
142
|
-
substring: String,
|
|
143
|
-
},
|
|
144
|
-
EndsWith {
|
|
145
|
-
receiver: Box<Self>,
|
|
146
|
-
substring: String,
|
|
147
|
-
},
|
|
148
|
-
First {
|
|
149
|
-
receiver: Box<Self>,
|
|
150
|
-
},
|
|
151
|
-
Last {
|
|
152
|
-
receiver: Box<Self>,
|
|
153
|
-
},
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/// Parses an invariant's `ast` node into an `Expr`.
|
|
157
|
-
///
|
|
158
|
-
/// An unrecognized `"op"`, or a malformed shape under a known one, is an `Err`.
|
|
159
|
-
pub fn parse(json: &Json) -> Result<Expr, String> {
|
|
160
|
-
let op = json
|
|
161
|
-
.get("op")
|
|
162
|
-
.and_then(Json::as_str)
|
|
163
|
-
.ok_or_else(|| format!("{json} has no string \"op\" field"))?;
|
|
164
|
-
|
|
165
|
-
let field = |name: &str| {
|
|
166
|
-
json.get(name)
|
|
167
|
-
.ok_or_else(|| format!("{op} node has no {name:?} field: {json}"))
|
|
168
|
-
};
|
|
169
|
-
let expr = |name: &str| parse(field(name)?);
|
|
170
|
-
let str_field = |name: &str| -> Result<String, String> {
|
|
171
|
-
field(name)?
|
|
172
|
-
.as_str()
|
|
173
|
-
.map(str::to_string)
|
|
174
|
-
.ok_or_else(|| format!("{op}'s {name:?} isn't a string: {json}"))
|
|
175
|
-
};
|
|
176
|
-
// `lookup.path` and `find.path` are segment arrays, never dotted strings.
|
|
177
|
-
let path_field = |name: &str| -> Result<Vec<String>, String> {
|
|
178
|
-
field(name)?
|
|
179
|
-
.as_array()
|
|
180
|
-
.ok_or_else(|| format!("{op}'s {name:?} isn't an array: {json}"))?
|
|
181
|
-
.iter()
|
|
182
|
-
.map(|v| {
|
|
183
|
-
v.as_str()
|
|
184
|
-
.map(str::to_string)
|
|
185
|
-
.ok_or_else(|| format!("{op}'s {name:?} has a non-string element: {json}"))
|
|
186
|
-
})
|
|
187
|
-
.collect::<Result<Vec<_>, _>>()
|
|
188
|
-
};
|
|
189
|
-
let comparison = || -> Result<Comparison, String> {
|
|
190
|
-
let cmp = field("cmp")?;
|
|
191
|
-
Ok(Comparison {
|
|
192
|
-
less_than: cmp
|
|
193
|
-
.get("less_than")
|
|
194
|
-
.and_then(Json::as_bool)
|
|
195
|
-
.ok_or_else(|| format!("{op}'s cmp has no less_than: {json}"))?,
|
|
196
|
-
equal: cmp
|
|
197
|
-
.get("equal")
|
|
198
|
-
.and_then(Json::as_bool)
|
|
199
|
-
.ok_or_else(|| format!("{op}'s cmp has no equal: {json}"))?,
|
|
200
|
-
negated: cmp
|
|
201
|
-
.get("negated")
|
|
202
|
-
.and_then(Json::as_bool)
|
|
203
|
-
.ok_or_else(|| format!("{op}'s cmp has no negated: {json}"))?,
|
|
204
|
-
})
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
Ok(match op {
|
|
208
|
-
"or" => Expr::Or {
|
|
209
|
-
left: Box::new(expr("left")?),
|
|
210
|
-
right: Box::new(expr("right")?),
|
|
211
|
-
},
|
|
212
|
-
"and" => Expr::And {
|
|
213
|
-
left: Box::new(expr("left")?),
|
|
214
|
-
right: Box::new(expr("right")?),
|
|
215
|
-
},
|
|
216
|
-
"not" => Expr::Not {
|
|
217
|
-
expr: Box::new(expr("expr")?),
|
|
218
|
-
},
|
|
219
|
-
"compare" => Expr::Compare {
|
|
220
|
-
cmp: comparison()?,
|
|
221
|
-
left: Box::new(expr("left")?),
|
|
222
|
-
right: Box::new(expr("right")?),
|
|
223
|
-
},
|
|
224
|
-
"include" => Expr::Include {
|
|
225
|
-
haystack: Box::new(expr("haystack")?),
|
|
226
|
-
needle: Box::new(expr("needle")?),
|
|
227
|
-
},
|
|
228
|
-
"int" => Expr::Int {
|
|
229
|
-
value: field("value")?
|
|
230
|
-
.as_i64()
|
|
231
|
-
.ok_or_else(|| format!("int's value isn't an integer: {json}"))?,
|
|
232
|
-
},
|
|
233
|
-
"float" => Expr::Float {
|
|
234
|
-
value: field("value")?
|
|
235
|
-
.as_f64()
|
|
236
|
-
.ok_or_else(|| format!("float's value isn't a number: {json}"))?,
|
|
237
|
-
},
|
|
238
|
-
"str" => Expr::Str {
|
|
239
|
-
value: str_field("value")?,
|
|
240
|
-
},
|
|
241
|
-
"bool" => Expr::Bool {
|
|
242
|
-
value: field("value")?
|
|
243
|
-
.as_bool()
|
|
244
|
-
.ok_or_else(|| format!("bool's value isn't a boolean: {json}"))?,
|
|
245
|
-
},
|
|
246
|
-
"nil" => Expr::Nil,
|
|
247
|
-
"add" => Expr::Add {
|
|
248
|
-
left: Box::new(expr("left")?),
|
|
249
|
-
right: Box::new(expr("right")?),
|
|
250
|
-
},
|
|
251
|
-
"sub" => Expr::Sub {
|
|
252
|
-
left: Box::new(expr("left")?),
|
|
253
|
-
right: Box::new(expr("right")?),
|
|
254
|
-
},
|
|
255
|
-
"mul" => Expr::Mul {
|
|
256
|
-
left: Box::new(expr("left")?),
|
|
257
|
-
right: Box::new(expr("right")?),
|
|
258
|
-
},
|
|
259
|
-
"div" => Expr::Div {
|
|
260
|
-
left: Box::new(expr("left")?),
|
|
261
|
-
right: Box::new(expr("right")?),
|
|
262
|
-
},
|
|
263
|
-
"sign_test" => Expr::SignTest {
|
|
264
|
-
cmp: comparison()?,
|
|
265
|
-
receiver: Box::new(expr("receiver")?),
|
|
266
|
-
},
|
|
267
|
-
"empty" => Expr::Empty {
|
|
268
|
-
receiver: Box::new(expr("receiver")?),
|
|
269
|
-
},
|
|
270
|
-
"to_s" => Expr::ToS {
|
|
271
|
-
receiver: Box::new(expr("receiver")?),
|
|
272
|
-
},
|
|
273
|
-
"modulo" => Expr::Modulo {
|
|
274
|
-
receiver: Box::new(expr("receiver")?),
|
|
275
|
-
divisor: Box::new(expr("divisor")?),
|
|
276
|
-
},
|
|
277
|
-
"size" => Expr::Size {
|
|
278
|
-
receiver: Box::new(expr("receiver")?),
|
|
279
|
-
},
|
|
280
|
-
"lookup" => Expr::Lookup {
|
|
281
|
-
path: path_field("path")?,
|
|
282
|
-
},
|
|
283
|
-
"block_predicate" => Expr::BlockPredicate {
|
|
284
|
-
mode: match str_field("mode")?.as_str() {
|
|
285
|
-
"all" => BlockMode::All,
|
|
286
|
-
"any" => BlockMode::Any,
|
|
287
|
-
"none" => BlockMode::None,
|
|
288
|
-
other => {
|
|
289
|
-
return Err(format!(
|
|
290
|
-
"block_predicate's mode {other:?} is none of all/any/none: {json}"
|
|
291
|
-
))
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
receiver: Box::new(expr("receiver")?),
|
|
295
|
-
param: str_field("param")?,
|
|
296
|
-
predicate: Box::new(expr("predicate")?),
|
|
297
|
-
},
|
|
298
|
-
"find" => Expr::Find {
|
|
299
|
-
receiver: Box::new(expr("receiver")?),
|
|
300
|
-
param: str_field("param")?,
|
|
301
|
-
predicate: Box::new(expr("predicate")?),
|
|
302
|
-
path: path_field("path")?,
|
|
303
|
-
},
|
|
304
|
-
"array" => Expr::Array {
|
|
305
|
-
elements: field("elements")?
|
|
306
|
-
.as_array()
|
|
307
|
-
.ok_or_else(|| format!("array's elements isn't an array: {json}"))?
|
|
308
|
-
.iter()
|
|
309
|
-
.map(parse)
|
|
310
|
-
.collect::<Result<Vec<_>, _>>()?,
|
|
311
|
-
},
|
|
312
|
-
"matches_regex" => Expr::MatchesRegex {
|
|
313
|
-
receiver: Box::new(expr("receiver")?),
|
|
314
|
-
pattern: str_field("pattern")?,
|
|
315
|
-
flags: str_field("flags")?,
|
|
316
|
-
},
|
|
317
|
-
"presence" => Expr::Presence {
|
|
318
|
-
receiver: Box::new(expr("receiver")?),
|
|
319
|
-
negated: field("negated")?
|
|
320
|
-
.as_bool()
|
|
321
|
-
.ok_or_else(|| format!("presence's negated isn't a boolean: {json}"))?,
|
|
322
|
-
},
|
|
323
|
-
"assignment" => Expr::Assignment {
|
|
324
|
-
receiver: Box::new(expr("receiver")?),
|
|
325
|
-
negated: field("negated")?
|
|
326
|
-
.as_bool()
|
|
327
|
-
.ok_or_else(|| format!("assignment's negated isn't a boolean: {json}"))?,
|
|
328
|
-
},
|
|
329
|
-
"split" => Expr::Split {
|
|
330
|
-
receiver: Box::new(expr("receiver")?),
|
|
331
|
-
separator: str_field("separator")?,
|
|
332
|
-
},
|
|
333
|
-
"strip" => Expr::Strip {
|
|
334
|
-
receiver: Box::new(expr("receiver")?),
|
|
335
|
-
side: match str_field("side")?.as_str() {
|
|
336
|
-
"both" => StripSide::Both,
|
|
337
|
-
"left" => StripSide::Left,
|
|
338
|
-
"right" => StripSide::Right,
|
|
339
|
-
other => {
|
|
340
|
-
return Err(format!(
|
|
341
|
-
"strip's side {other:?} is none of both/left/right: {json}"
|
|
342
|
-
))
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
"starts_with" => Expr::StartsWith {
|
|
347
|
-
receiver: Box::new(expr("receiver")?),
|
|
348
|
-
substring: str_field("substring")?,
|
|
349
|
-
},
|
|
350
|
-
"ends_with" => Expr::EndsWith {
|
|
351
|
-
receiver: Box::new(expr("receiver")?),
|
|
352
|
-
substring: str_field("substring")?,
|
|
353
|
-
},
|
|
354
|
-
"first" => Expr::First {
|
|
355
|
-
receiver: Box::new(expr("receiver")?),
|
|
356
|
-
},
|
|
357
|
-
"last" => Expr::Last {
|
|
358
|
-
receiver: Box::new(expr("receiver")?),
|
|
359
|
-
},
|
|
360
|
-
other => return Err(format!("unrecognized expression op {other:?}: {json}")),
|
|
361
|
-
})
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/// The runtime value an `Expr` evaluates to.
|
|
365
|
-
///
|
|
366
|
-
/// Lists are a single `Array` variant: every field here is already a materialised JSON value.
|
|
367
|
-
/// An `Object` is a multi-field value object, kept as the JSON it arrived as; a one-field
|
|
368
|
-
/// object never survives a lookup (see `unwrap_scalar`).
|
|
369
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
370
|
-
pub enum Value {
|
|
371
|
-
Int(i64),
|
|
372
|
-
Float(f64),
|
|
373
|
-
Str(String),
|
|
374
|
-
Bool(bool),
|
|
375
|
-
Nil,
|
|
376
|
-
Array(Vec<Self>),
|
|
377
|
-
Object(Json),
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
impl Value {
|
|
381
|
-
/// Ruby truthiness: everything but `Nil` and `false`.
|
|
382
|
-
///
|
|
383
|
-
/// Invariant results are checked for truthiness, not `== true`, as `given`/`ensures` are.
|
|
384
|
-
pub(crate) const fn truthy(&self) -> bool {
|
|
385
|
-
!matches!(self, Self::Nil | Self::Bool(false))
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
fn from_json(json: &Json) -> Result<Self, String> {
|
|
389
|
-
match json {
|
|
390
|
-
Json::Null => Ok(Self::Nil),
|
|
391
|
-
Json::Bool(b) => Ok(Self::Bool(*b)),
|
|
392
|
-
Json::Number(n) => n
|
|
393
|
-
.as_i64()
|
|
394
|
-
.map(Self::Int)
|
|
395
|
-
.or_else(|| n.as_f64().map(Self::Float))
|
|
396
|
-
.ok_or_else(|| format!("{n} is a number this interpreter cannot represent")),
|
|
397
|
-
Json::String(s) => Ok(Self::Str(s.clone())),
|
|
398
|
-
Json::Array(items) => Ok(Self::Array(
|
|
399
|
-
items
|
|
400
|
-
.iter()
|
|
401
|
-
.map(Self::from_json)
|
|
402
|
-
.collect::<Result<Vec<_>, _>>()?,
|
|
403
|
-
)),
|
|
404
|
-
Json::Object(_) => Ok(Self::Object(json.clone())),
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/// The value as an `f64` when it is an `Int` or `Float`.
|
|
410
|
-
const fn numeric(v: &Value) -> Option<f64> {
|
|
411
|
-
match v {
|
|
412
|
-
Value::Int(i) => Some(*i as f64),
|
|
413
|
-
Value::Float(f) => Some(*f),
|
|
414
|
-
_ => None,
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/// An `Int` and a `Float` are equal only when the float is a whole number that is that integer.
|
|
419
|
-
fn int_equals_float(i: i64, f: f64) -> bool {
|
|
420
|
-
f.is_finite()
|
|
421
|
-
&& f.fract() == 0.0
|
|
422
|
-
&& (-9.223_372_036_854_776e18..9.223_372_036_854_776e18).contains(&f)
|
|
423
|
-
&& f as i64 == i
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/// Ruby's `==` after `Evaluator.equal?`: numbers compare across kinds, and lists and objects
|
|
427
|
-
/// compare element by element with the same rule, so `[1] == [1.0]`.
|
|
428
|
-
#[allow(clippy::float_cmp)] // reason: Ruby's `Float#==` is exact, and this mirrors it
|
|
429
|
-
fn values_equal(l: &Value, r: &Value) -> bool {
|
|
430
|
-
match (l, r) {
|
|
431
|
-
(Value::Int(a), Value::Int(b)) => a == b,
|
|
432
|
-
(Value::Int(i), Value::Float(f)) | (Value::Float(f), Value::Int(i)) => {
|
|
433
|
-
int_equals_float(*i, *f)
|
|
434
|
-
}
|
|
435
|
-
(Value::Float(a), Value::Float(b)) => a == b,
|
|
436
|
-
(Value::Array(a), Value::Array(b)) => {
|
|
437
|
-
a.len() == b.len() && a.iter().zip(b).all(|(x, y)| values_equal(x, y))
|
|
438
|
-
}
|
|
439
|
-
(Value::Object(Json::Object(a)), Value::Object(Json::Object(b))) => {
|
|
440
|
-
a.len() == b.len()
|
|
441
|
-
&& a.iter()
|
|
442
|
-
.all(|(key, x)| match (b.get(key), Value::from_json(x)) {
|
|
443
|
-
(Some(y), Ok(x)) => Value::from_json(y).is_ok_and(|y| values_equal(&x, &y)),
|
|
444
|
-
_ => false,
|
|
445
|
-
})
|
|
446
|
-
}
|
|
447
|
-
_ => l == r,
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/// Orders numbers, then strings; any other pairing is an error worded as Ruby's.
|
|
452
|
-
fn less_than(l: &Value, r: &Value) -> Result<bool, String> {
|
|
453
|
-
match (l, r) {
|
|
454
|
-
(Value::Int(a), Value::Int(b)) => Ok(a < b),
|
|
455
|
-
_ => match (numeric(l), numeric(r)) {
|
|
456
|
-
(Some(a), Some(b)) => Ok(a < b),
|
|
457
|
-
_ => match (l, r) {
|
|
458
|
-
(Value::Str(a), Value::Str(b)) => Ok(a < b),
|
|
459
|
-
_ => Err(format!(
|
|
460
|
-
"comparison of {} with {} failed",
|
|
461
|
-
class_of(l),
|
|
462
|
-
describe(r)
|
|
463
|
-
)),
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/// ORs `<` and `==`, then negates if the operator says so; `SignTest` reuses it against 0.
|
|
470
|
-
fn apply_comparison(op: Comparison, l: &Value, r: &Value) -> Result<bool, String> {
|
|
471
|
-
let lt = op.less_than && less_than(l, r)?;
|
|
472
|
-
let eq = op.equal && values_equal(l, r);
|
|
473
|
-
Ok(if op.negated { !(lt || eq) } else { lt || eq })
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/// Stringifies a scalar as Ruby's `to_s` does (`Nil` becomes `""`); a list or object is an error.
|
|
477
|
-
fn to_s(v: &Value) -> Result<String, String> {
|
|
478
|
-
match v {
|
|
479
|
-
Value::Str(s) => Ok(s.clone()),
|
|
480
|
-
Value::Int(i) => Ok(i.to_string()),
|
|
481
|
-
Value::Float(f) => Ok(ruby_float(*f)),
|
|
482
|
-
Value::Bool(b) => Ok(b.to_string()),
|
|
483
|
-
Value::Nil => Ok(String::new()),
|
|
484
|
-
Value::Array(_) | Value::Object(_) => {
|
|
485
|
-
Err(format!("to_s expects a scalar, got {}", describe(v)))
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/// Ruby's `Float#to_s`: shortest round-trip digits, fixed notation for exponents -4 through 14,
|
|
491
|
-
/// `d.ddde+XX` outside that.
|
|
492
|
-
#[allow(clippy::expect_used)] // reason: Rust's exponent format always has `e` and an integer exponent
|
|
493
|
-
fn ruby_float(f: f64) -> String {
|
|
494
|
-
if f.is_nan() {
|
|
495
|
-
return "NaN".to_string();
|
|
496
|
-
}
|
|
497
|
-
if f.is_infinite() {
|
|
498
|
-
return if f < 0.0 { "-Infinity" } else { "Infinity" }.to_string();
|
|
499
|
-
}
|
|
500
|
-
let sign = if f.is_sign_negative() { "-" } else { "" };
|
|
501
|
-
if f == 0.0 {
|
|
502
|
-
return format!("{sign}0.0");
|
|
503
|
-
}
|
|
504
|
-
let scientific = format!("{:e}", f.abs());
|
|
505
|
-
let (mantissa, exponent) = scientific
|
|
506
|
-
.split_once('e')
|
|
507
|
-
.expect("exponent form always has an exponent");
|
|
508
|
-
let exponent: i32 = exponent.parse().expect("exponent is an integer");
|
|
509
|
-
let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
|
|
510
|
-
if (-4..15).contains(&exponent) {
|
|
511
|
-
if exponent >= 0 {
|
|
512
|
-
let whole = exponent as usize + 1;
|
|
513
|
-
let (int_part, frac_part) = if digits.len() > whole {
|
|
514
|
-
(digits[..whole].to_string(), digits[whole..].to_string())
|
|
515
|
-
} else {
|
|
516
|
-
(
|
|
517
|
-
format!("{digits}{}", "0".repeat(whole - digits.len())),
|
|
518
|
-
"0".to_string(),
|
|
519
|
-
)
|
|
520
|
-
};
|
|
521
|
-
format!("{sign}{int_part}.{frac_part}")
|
|
522
|
-
} else {
|
|
523
|
-
format!("{sign}0.{}{digits}", "0".repeat((-exponent - 1) as usize))
|
|
524
|
-
}
|
|
525
|
-
} else {
|
|
526
|
-
let frac = if digits.len() > 1 { &digits[1..] } else { "0" };
|
|
527
|
-
format!(
|
|
528
|
-
"{sign}{}.{frac}e{}{:02}",
|
|
529
|
-
&digits[..1],
|
|
530
|
-
if exponent < 0 { '-' } else { '+' },
|
|
531
|
-
exponent.abs()
|
|
532
|
-
)
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/// Ruby's `String#inspect` for a UTF-8 string.
|
|
537
|
-
fn ruby_inspect(s: &str) -> String {
|
|
538
|
-
use std::fmt::Write;
|
|
539
|
-
let mut out = String::from("\"");
|
|
540
|
-
let mut chars = s.chars().peekable();
|
|
541
|
-
while let Some(c) = chars.next() {
|
|
542
|
-
match c {
|
|
543
|
-
'"' => out.push_str("\\\""),
|
|
544
|
-
'\\' => out.push_str("\\\\"),
|
|
545
|
-
'\n' => out.push_str("\\n"),
|
|
546
|
-
'\t' => out.push_str("\\t"),
|
|
547
|
-
'\r' => out.push_str("\\r"),
|
|
548
|
-
'\u{07}' => out.push_str("\\a"),
|
|
549
|
-
'\u{08}' => out.push_str("\\b"),
|
|
550
|
-
'\u{0b}' => out.push_str("\\v"),
|
|
551
|
-
'\u{0c}' => out.push_str("\\f"),
|
|
552
|
-
'\u{1b}' => out.push_str("\\e"),
|
|
553
|
-
'#' if matches!(chars.peek(), Some('{' | '$' | '@')) => out.push_str("\\#"),
|
|
554
|
-
c if (c as u32) < 0x20 || c as u32 == 0x7f => {
|
|
555
|
-
let _ = write!(out, "\\u{:04X}", c as u32); // writing to a String cannot fail
|
|
556
|
-
}
|
|
557
|
-
c => out.push(c),
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
out.push('"');
|
|
561
|
-
out
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/// Ruby's `JSON.generate` for a value: what `Rendering.describe` prints for a list or hash.
|
|
565
|
-
fn generate(v: &Value) -> String {
|
|
566
|
-
match v {
|
|
567
|
-
Value::Nil => "null".to_string(),
|
|
568
|
-
Value::Bool(b) => b.to_string(),
|
|
569
|
-
Value::Int(i) => i.to_string(),
|
|
570
|
-
Value::Float(f) => ruby_float(*f),
|
|
571
|
-
Value::Str(s) => Json::String(s.clone()).to_string(),
|
|
572
|
-
Value::Array(items) => format!(
|
|
573
|
-
"[{}]",
|
|
574
|
-
items.iter().map(generate).collect::<Vec<_>>().join(",")
|
|
575
|
-
),
|
|
576
|
-
Value::Object(json) => json.to_string(),
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/// Ruby's `Rendering.describe`: a value as an error message names it.
|
|
581
|
-
fn describe(v: &Value) -> String {
|
|
582
|
-
match v {
|
|
583
|
-
Value::Nil => "nil".to_string(),
|
|
584
|
-
Value::Str(s) => ruby_inspect(s),
|
|
585
|
-
other => generate(other),
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/// The Ruby class name an error message gives for a value; `nil` for nil, as `class_of` does.
|
|
590
|
-
const fn class_of(v: &Value) -> &'static str {
|
|
591
|
-
match v {
|
|
592
|
-
Value::Nil => "nil",
|
|
593
|
-
Value::Int(_) => "Integer",
|
|
594
|
-
Value::Float(_) => "Float",
|
|
595
|
-
Value::Str(_) => "String",
|
|
596
|
-
Value::Bool(true) => "TrueClass",
|
|
597
|
-
Value::Bool(false) => "FalseClass",
|
|
598
|
-
Value::Array(_) => "Array",
|
|
599
|
-
// A value object's own class name is not in the JSON; Hash is the nearest honest answer.
|
|
600
|
-
Value::Object(_) => "Hash",
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/// Requires an `Int` or `Float`, worded as `Resolver.require_number`.
|
|
605
|
-
fn require_number(v: &Value, operation: &str) -> Result<Value, String> {
|
|
606
|
-
match v {
|
|
607
|
-
Value::Int(_) | Value::Float(_) => Ok(v.clone()),
|
|
608
|
-
other => Err(format!(
|
|
609
|
-
"{operation} expects a number, got {}",
|
|
610
|
-
describe(other)
|
|
611
|
-
)),
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/// `Resolver.add`: 64-bit integer sums that overflow, and float sums that are not finite, fail.
|
|
616
|
-
fn add(left: &Value, right: &Value) -> Result<Value, String> {
|
|
617
|
-
let lhs = require_number(left, "addition")?;
|
|
618
|
-
let rhs = require_number(right, "addition")?;
|
|
619
|
-
if let (Value::Int(a), Value::Int(b)) = (&lhs, &rhs) {
|
|
620
|
-
return a.checked_add(*b).map(Value::Int).ok_or_else(|| {
|
|
621
|
-
format!("addition overflowed: {a} + {b} does not fit in a 64-bit integer")
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
let sum = numeric(&lhs).unwrap_or_default() + numeric(&rhs).unwrap_or_default();
|
|
625
|
-
if sum.is_finite() {
|
|
626
|
-
Ok(Value::Float(sum))
|
|
627
|
-
} else {
|
|
628
|
-
Err(format!(
|
|
629
|
-
"addition overflowed: {} + {} is not a finite number",
|
|
630
|
-
to_s(&lhs)?,
|
|
631
|
-
to_s(&rhs)?
|
|
632
|
-
))
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/// `Resolver.checked_arithmetic`: the same 64-bit and finiteness limits `add` holds to, named for
|
|
637
|
-
/// the operation that overflowed.
|
|
638
|
-
fn arithmetic(
|
|
639
|
-
name: &str,
|
|
640
|
-
symbol: &str,
|
|
641
|
-
left: &Value,
|
|
642
|
-
right: &Value,
|
|
643
|
-
int_op: fn(i64, i64) -> Option<i64>,
|
|
644
|
-
float_op: fn(f64, f64) -> f64,
|
|
645
|
-
) -> Result<Value, String> {
|
|
646
|
-
let lhs = require_number(left, name)?;
|
|
647
|
-
let rhs = require_number(right, name)?;
|
|
648
|
-
if let (Value::Int(a), Value::Int(b)) = (&lhs, &rhs) {
|
|
649
|
-
return int_op(*a, *b).map(Value::Int).ok_or_else(|| {
|
|
650
|
-
format!("{name} overflowed: {a} {symbol} {b} does not fit in a 64-bit integer")
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
let result = float_op(
|
|
654
|
-
numeric(&lhs).unwrap_or_default(),
|
|
655
|
-
numeric(&rhs).unwrap_or_default(),
|
|
656
|
-
);
|
|
657
|
-
if result.is_finite() {
|
|
658
|
-
Ok(Value::Float(result))
|
|
659
|
-
} else {
|
|
660
|
-
Err(format!(
|
|
661
|
-
"{name} overflowed: {} {symbol} {} is not a finite number",
|
|
662
|
-
to_s(&lhs)?,
|
|
663
|
-
to_s(&rhs)?
|
|
664
|
-
))
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/// `Resolver.divide`: a zero divisor faults first; integers floor toward negative infinity.
|
|
669
|
-
fn divide(left: &Value, right: &Value) -> Result<Value, String> {
|
|
670
|
-
if numeric(&require_number(right, "division")?) == Some(0.0) {
|
|
671
|
-
return Err("divided by 0".to_string());
|
|
672
|
-
}
|
|
673
|
-
arithmetic("division", "/", left, right, floor_div, |a, b| a / b)
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
/// Floored integer division; `None` only for `i64::MIN / -1`, which does not fit in 64 bits.
|
|
677
|
-
fn floor_div(a: i64, b: i64) -> Option<i64> {
|
|
678
|
-
let quotient = a.checked_div(b)?;
|
|
679
|
-
Some(if a % b != 0 && ((a < 0) != (b < 0)) {
|
|
680
|
-
quotient - 1
|
|
681
|
-
} else {
|
|
682
|
-
quotient
|
|
683
|
-
})
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/// `Resolver.apply_modulo`: floored like Ruby's `%`, so the result takes the divisor's sign.
|
|
687
|
-
fn modulo(receiver: &Value, divisor: &Value) -> Result<Value, String> {
|
|
688
|
-
let receiver = require_number(receiver, "modulo")?;
|
|
689
|
-
let divisor = require_number(divisor, "modulo")?;
|
|
690
|
-
if numeric(&divisor) == Some(0.0) {
|
|
691
|
-
return Err("divided by 0".to_string());
|
|
692
|
-
}
|
|
693
|
-
if let (Value::Int(r), Value::Int(d)) = (&receiver, &divisor) {
|
|
694
|
-
// `i64::MIN % -1` overflows in Rust; Ruby answers 0.
|
|
695
|
-
if *d == -1 {
|
|
696
|
-
return Ok(Value::Int(0));
|
|
697
|
-
}
|
|
698
|
-
let raw = r % d;
|
|
699
|
-
return Ok(Value::Int(if raw != 0 && (raw < 0) != (*d < 0) {
|
|
700
|
-
raw + d
|
|
701
|
-
} else {
|
|
702
|
-
raw
|
|
703
|
-
}));
|
|
704
|
-
}
|
|
705
|
-
let x = numeric(&receiver).unwrap_or_default();
|
|
706
|
-
let y = numeric(&divisor).unwrap_or_default();
|
|
707
|
-
let remainder = if y.is_infinite() && !x.is_infinite() {
|
|
708
|
-
x
|
|
709
|
-
} else {
|
|
710
|
-
x % y
|
|
711
|
-
};
|
|
712
|
-
Ok(Value::Float(if y * remainder < 0.0 {
|
|
713
|
-
remainder + y
|
|
714
|
-
} else {
|
|
715
|
-
remainder
|
|
716
|
-
}))
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
/// `Resolver.blank?`: nil, false, and an empty string, list or object.
|
|
720
|
-
///
|
|
721
|
-
/// Ruby's version judges a list by its own emptiness, so a non-empty list is present in both
|
|
722
|
-
/// hosts, whether or not it is made of pairs.
|
|
723
|
-
fn blank(v: &Value) -> bool {
|
|
724
|
-
match v {
|
|
725
|
-
Value::Nil | Value::Bool(false) => true,
|
|
726
|
-
Value::Str(s) => s.is_empty(),
|
|
727
|
-
Value::Array(items) => items.is_empty(),
|
|
728
|
-
Value::Object(json) => json.as_object().is_some_and(serde_json::Map::is_empty),
|
|
729
|
-
_ => false,
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/// Ruby's `String#split` with a string separator: `" "` splits on runs of whitespace, `""` on
|
|
734
|
-
/// characters, anything else on the literal text; trailing empty fields are dropped.
|
|
735
|
-
fn split(text: &str, separator: &str) -> Vec<Value> {
|
|
736
|
-
let mut parts: Vec<String> = match separator {
|
|
737
|
-
" " => text
|
|
738
|
-
.split([' ', '\t', '\n', '\u{0b}', '\u{0c}', '\r'])
|
|
739
|
-
.filter(|p| !p.is_empty())
|
|
740
|
-
.map(str::to_string)
|
|
741
|
-
.collect(),
|
|
742
|
-
"" => text.chars().map(String::from).collect(),
|
|
743
|
-
sep => text.split(sep).map(str::to_string).collect(),
|
|
744
|
-
};
|
|
745
|
-
while parts.last().is_some_and(String::is_empty) {
|
|
746
|
-
parts.pop();
|
|
747
|
-
}
|
|
748
|
-
parts.into_iter().map(Value::Str).collect()
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
/// Ruby's `String#strip` family: trims null, tab, line feed, vertical tab, form feed, carriage
|
|
752
|
-
/// return and space, never a Unicode space (Rust's own `trim` would drop U+00A0 too).
|
|
753
|
-
fn strip(text: &str, side: StripSide) -> String {
|
|
754
|
-
let stripped = |c: char| matches!(c, '\0' | '\t' | '\n' | '\u{b}' | '\u{c}' | '\r' | ' ');
|
|
755
|
-
match side {
|
|
756
|
-
StripSide::Both => text.trim_matches(stripped),
|
|
757
|
-
StripSide::Left => text.trim_start_matches(stripped),
|
|
758
|
-
StripSide::Right => text.trim_end_matches(stripped),
|
|
759
|
-
}
|
|
760
|
-
.to_string()
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
/// Compiles a Ruby `Regexp` source for the `regex` crate.
|
|
764
|
-
///
|
|
765
|
-
/// Ruby's `\d \w \s \h` are ASCII and its `^ $` are always line anchors, where `regex` reads
|
|
766
|
-
/// the classes as Unicode and the anchors as text anchors, so both are rewritten. `m` is
|
|
767
|
-
/// Ruby's dot-matches-newline; `\Z` (end, or before a final newline) becomes `\n?\z`.
|
|
768
|
-
fn ruby_regex(pattern: &str, flags: &str) -> Result<regex::Regex, String> {
|
|
769
|
-
use std::fmt::Write;
|
|
770
|
-
let mut source = String::new();
|
|
771
|
-
let mut in_class = false;
|
|
772
|
-
let mut class_start = 0;
|
|
773
|
-
let chars: Vec<char> = pattern.chars().collect();
|
|
774
|
-
let mut index = 0;
|
|
775
|
-
while index < chars.len() {
|
|
776
|
-
let c = chars[index];
|
|
777
|
-
if c == '\\' {
|
|
778
|
-
let Some(&next) = chars.get(index + 1) else {
|
|
779
|
-
source.push(c);
|
|
780
|
-
break;
|
|
781
|
-
};
|
|
782
|
-
index += 2;
|
|
783
|
-
let (positive, negative) = match next.to_ascii_lowercase() {
|
|
784
|
-
'd' => ("0-9", "[^0-9]"),
|
|
785
|
-
'w' => ("A-Za-z0-9_", "[^A-Za-z0-9_]"),
|
|
786
|
-
's' => ("\\x20\\t\\r\\n\\f\\x0B", "[^\\x20\\t\\r\\n\\f\\x0B]"),
|
|
787
|
-
'h' => ("0-9a-fA-F", "[^0-9a-fA-F]"),
|
|
788
|
-
_ => {
|
|
789
|
-
match next {
|
|
790
|
-
'Z' if !in_class => source.push_str("(?:\\n?\\z)"),
|
|
791
|
-
_ => {
|
|
792
|
-
source.push(c);
|
|
793
|
-
source.push(next);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
continue;
|
|
797
|
-
}
|
|
798
|
-
};
|
|
799
|
-
match (next.is_ascii_uppercase(), in_class) {
|
|
800
|
-
(false, true) => source.push_str(positive),
|
|
801
|
-
(false, false) => {
|
|
802
|
-
let _ = write!(source, "[{positive}]"); // writing to a String cannot fail
|
|
803
|
-
}
|
|
804
|
-
(true, _) => source.push_str(negative),
|
|
805
|
-
}
|
|
806
|
-
continue;
|
|
807
|
-
}
|
|
808
|
-
if in_class {
|
|
809
|
-
if c == ']' && index != class_start {
|
|
810
|
-
in_class = false;
|
|
811
|
-
}
|
|
812
|
-
} else if c == '[' {
|
|
813
|
-
in_class = true;
|
|
814
|
-
class_start = index + 1;
|
|
815
|
-
if chars.get(class_start) == Some(&'^') {
|
|
816
|
-
class_start += 1;
|
|
817
|
-
}
|
|
818
|
-
} else if c == '^' || c == '$' {
|
|
819
|
-
source.push_str(if c == '^' { "(?m:^)" } else { "(?m:$)" });
|
|
820
|
-
index += 1;
|
|
821
|
-
continue;
|
|
822
|
-
}
|
|
823
|
-
source.push(c);
|
|
824
|
-
index += 1;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
regex::RegexBuilder::new(&source)
|
|
828
|
-
.case_insensitive(flags.contains('i'))
|
|
829
|
-
.dot_matches_new_line(flags.contains('m'))
|
|
830
|
-
.ignore_whitespace(flags.contains('x'))
|
|
831
|
-
.build()
|
|
832
|
-
.map_err(|e| {
|
|
833
|
-
format!(
|
|
834
|
-
"match? given an invalid pattern {} — {e}",
|
|
835
|
-
ruby_inspect(pattern)
|
|
836
|
-
)
|
|
837
|
-
})
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
/// One block parameter bound to its element, innermost first; `attrs` in `Resolver#fetch`.
|
|
841
|
-
struct Scope<'a> {
|
|
842
|
-
name: &'a str,
|
|
843
|
-
value: Value,
|
|
844
|
-
outer: Option<&'a Self>,
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
impl Scope<'_> {
|
|
848
|
-
fn get(&self, name: &str) -> Option<&Value> {
|
|
849
|
-
if self.name == name {
|
|
850
|
-
Some(&self.value)
|
|
851
|
-
} else {
|
|
852
|
-
self.outer.and_then(|outer| outer.get(name))
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
/// Evaluates `expr` against a value object's JSON fields.
|
|
858
|
-
///
|
|
859
|
-
/// Every node the Ruby runtime (`Evaluator`/`Resolver`) has is interpreted, with its semantics,
|
|
860
|
-
/// null handling and error wording. No command arguments are in scope, so `Lookup` reads
|
|
861
|
-
/// `instance` alone, after any enclosing block parameters.
|
|
862
|
-
pub fn interpret(expr: &Expr, instance: &Json) -> Result<Value, String> {
|
|
863
|
-
eval(expr, instance, None)
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
fn eval(expr: &Expr, instance: &Json, scope: Option<&Scope>) -> Result<Value, String> {
|
|
867
|
-
let recur = |e: &Expr| eval(e, instance, scope);
|
|
868
|
-
match expr {
|
|
869
|
-
Expr::Int { value } => Ok(Value::Int(*value)),
|
|
870
|
-
Expr::Float { value } => Ok(Value::Float(*value)),
|
|
871
|
-
Expr::Str { value } => Ok(Value::Str(value.clone())),
|
|
872
|
-
Expr::Bool { value } => Ok(Value::Bool(*value)),
|
|
873
|
-
Expr::Nil => Ok(Value::Nil),
|
|
874
|
-
Expr::Array { elements } => Ok(Value::Array(
|
|
875
|
-
elements.iter().map(recur).collect::<Result<Vec<_>, _>>()?,
|
|
876
|
-
)),
|
|
877
|
-
Expr::Lookup { path } => lookup(path, instance, scope),
|
|
878
|
-
Expr::Or { left, right } => {
|
|
879
|
-
Ok(Value::Bool(recur(left)?.truthy() || recur(right)?.truthy()))
|
|
880
|
-
}
|
|
881
|
-
Expr::And { left, right } => {
|
|
882
|
-
Ok(Value::Bool(recur(left)?.truthy() && recur(right)?.truthy()))
|
|
883
|
-
}
|
|
884
|
-
Expr::Not { expr } => Ok(Value::Bool(!recur(expr)?.truthy())),
|
|
885
|
-
Expr::Compare { cmp, left, right } => Ok(Value::Bool(apply_comparison(
|
|
886
|
-
*cmp,
|
|
887
|
-
&recur(left)?,
|
|
888
|
-
&recur(right)?,
|
|
889
|
-
)?)),
|
|
890
|
-
// The needle resolves before the haystack, as `Evaluator.includes?` does.
|
|
891
|
-
Expr::Include { haystack, needle } => {
|
|
892
|
-
let wanted = recur(needle)?;
|
|
893
|
-
match recur(haystack)? {
|
|
894
|
-
Value::Array(items) => Ok(Value::Bool(
|
|
895
|
-
items.iter().any(|item| values_equal(item, &wanted)),
|
|
896
|
-
)),
|
|
897
|
-
Value::Str(text) => match &wanted {
|
|
898
|
-
Value::Str(part) => Ok(Value::Bool(text.contains(part.as_str()))),
|
|
899
|
-
other => Err(format!(
|
|
900
|
-
"no implicit conversion of {} into String",
|
|
901
|
-
class_of(other)
|
|
902
|
-
)),
|
|
903
|
-
},
|
|
904
|
-
_ => Ok(Value::Bool(false)),
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
Expr::Add { left, right } => add(&recur(left)?, &recur(right)?),
|
|
908
|
-
Expr::Sub { left, right } => arithmetic(
|
|
909
|
-
"subtraction",
|
|
910
|
-
"-",
|
|
911
|
-
&recur(left)?,
|
|
912
|
-
&recur(right)?,
|
|
913
|
-
i64::checked_sub,
|
|
914
|
-
|a, b| a - b,
|
|
915
|
-
),
|
|
916
|
-
Expr::Mul { left, right } => arithmetic(
|
|
917
|
-
"multiplication",
|
|
918
|
-
"*",
|
|
919
|
-
&recur(left)?,
|
|
920
|
-
&recur(right)?,
|
|
921
|
-
i64::checked_mul,
|
|
922
|
-
|a, b| a * b,
|
|
923
|
-
),
|
|
924
|
-
Expr::Div { left, right } => divide(&recur(left)?, &recur(right)?),
|
|
925
|
-
Expr::Modulo { receiver, divisor } => modulo(&recur(receiver)?, &recur(divisor)?),
|
|
926
|
-
Expr::SignTest { cmp, receiver } => {
|
|
927
|
-
let v = recur(receiver)?;
|
|
928
|
-
if numeric(&v).is_none() {
|
|
929
|
-
return Err(format!(
|
|
930
|
-
"{} expects a number, got {}",
|
|
931
|
-
sign_test_name(*cmp),
|
|
932
|
-
describe(&v)
|
|
933
|
-
));
|
|
934
|
-
}
|
|
935
|
-
Ok(Value::Bool(apply_comparison(*cmp, &v, &Value::Int(0))?))
|
|
936
|
-
}
|
|
937
|
-
Expr::Empty { receiver } => match recur(receiver)? {
|
|
938
|
-
Value::Str(s) => Ok(Value::Bool(s.is_empty())),
|
|
939
|
-
Value::Array(items) => Ok(Value::Bool(items.is_empty())),
|
|
940
|
-
other => Err(format!(
|
|
941
|
-
"empty? expects a list or string, got {}",
|
|
942
|
-
describe(&other)
|
|
943
|
-
)),
|
|
944
|
-
},
|
|
945
|
-
Expr::Size { receiver } => match recur(receiver)? {
|
|
946
|
-
Value::Str(s) => Ok(Value::Int(s.chars().count() as i64)),
|
|
947
|
-
Value::Array(items) => Ok(Value::Int(items.len() as i64)),
|
|
948
|
-
other => Err(format!(
|
|
949
|
-
"size expects a list or string, got {}",
|
|
950
|
-
describe(&other)
|
|
951
|
-
)),
|
|
952
|
-
},
|
|
953
|
-
Expr::ToS { receiver } => Ok(Value::Str(to_s(&recur(receiver)?)?)),
|
|
954
|
-
Expr::MatchesRegex {
|
|
955
|
-
receiver,
|
|
956
|
-
pattern,
|
|
957
|
-
flags,
|
|
958
|
-
} => {
|
|
959
|
-
let text = match recur(receiver)? {
|
|
960
|
-
Value::Str(s) => s,
|
|
961
|
-
v @ (Value::Int(_) | Value::Float(_)) => to_s(&v)?,
|
|
962
|
-
Value::Nil => String::new(),
|
|
963
|
-
other => return Err(format!("match? expects a scalar, got {}", class_of(&other))),
|
|
964
|
-
};
|
|
965
|
-
Ok(Value::Bool(ruby_regex(pattern, flags)?.is_match(&text)))
|
|
966
|
-
}
|
|
967
|
-
Expr::Presence { receiver, negated } => {
|
|
968
|
-
Ok(Value::Bool(blank(&recur(receiver)?) == *negated))
|
|
969
|
-
}
|
|
970
|
-
Expr::Assignment { receiver, negated } => {
|
|
971
|
-
Ok(Value::Bool((recur(receiver)? == Value::Nil) == *negated))
|
|
972
|
-
}
|
|
973
|
-
Expr::Split {
|
|
974
|
-
receiver,
|
|
975
|
-
separator,
|
|
976
|
-
} => match recur(receiver)? {
|
|
977
|
-
Value::Str(text) => Ok(Value::Array(split(&text, separator))),
|
|
978
|
-
other => Err(format!("split expects a string, got {}", describe(&other))),
|
|
979
|
-
},
|
|
980
|
-
Expr::First { receiver } => match recur(receiver)? {
|
|
981
|
-
Value::Array(items) => Ok(items.into_iter().next().unwrap_or(Value::Nil)),
|
|
982
|
-
other => Err(format!("first expects a list, got {}", describe(&other))),
|
|
983
|
-
},
|
|
984
|
-
Expr::Last { receiver } => match recur(receiver)? {
|
|
985
|
-
Value::Array(items) => Ok(items.into_iter().next_back().unwrap_or(Value::Nil)),
|
|
986
|
-
other => Err(format!("last expects a list, got {}", describe(&other))),
|
|
987
|
-
},
|
|
988
|
-
Expr::Strip { receiver, side } => match recur(receiver)? {
|
|
989
|
-
Value::Str(text) => Ok(Value::Str(strip(&text, *side))),
|
|
990
|
-
other => Err(format!("strip expects a string, got {}", describe(&other))),
|
|
991
|
-
},
|
|
992
|
-
Expr::StartsWith {
|
|
993
|
-
receiver,
|
|
994
|
-
substring,
|
|
995
|
-
} => match recur(receiver)? {
|
|
996
|
-
Value::Str(text) => Ok(Value::Bool(text.starts_with(substring.as_str()))),
|
|
997
|
-
other => Err(format!(
|
|
998
|
-
"start_with? expects a string, got {}",
|
|
999
|
-
describe(&other)
|
|
1000
|
-
)),
|
|
1001
|
-
},
|
|
1002
|
-
Expr::EndsWith {
|
|
1003
|
-
receiver,
|
|
1004
|
-
substring,
|
|
1005
|
-
} => match recur(receiver)? {
|
|
1006
|
-
Value::Str(text) => Ok(Value::Bool(text.ends_with(substring.as_str()))),
|
|
1007
|
-
other => Err(format!(
|
|
1008
|
-
"end_with? expects a string, got {}",
|
|
1009
|
-
describe(&other)
|
|
1010
|
-
)),
|
|
1011
|
-
},
|
|
1012
|
-
// Every element is evaluated before aggregating, so an error on any element surfaces as
|
|
1013
|
-
// it does in Ruby.
|
|
1014
|
-
Expr::BlockPredicate {
|
|
1015
|
-
mode,
|
|
1016
|
-
receiver,
|
|
1017
|
-
param,
|
|
1018
|
-
predicate,
|
|
1019
|
-
} => {
|
|
1020
|
-
let mode_name = match mode {
|
|
1021
|
-
BlockMode::All => "all",
|
|
1022
|
-
BlockMode::Any => "any",
|
|
1023
|
-
BlockMode::None => "none",
|
|
1024
|
-
};
|
|
1025
|
-
let items = match recur(receiver)? {
|
|
1026
|
-
Value::Array(items) => items,
|
|
1027
|
-
other => {
|
|
1028
|
-
return Err(format!(
|
|
1029
|
-
"{mode_name}? expects a list, got {}",
|
|
1030
|
-
describe(&other)
|
|
1031
|
-
))
|
|
1032
|
-
}
|
|
1033
|
-
};
|
|
1034
|
-
let mut outcomes = Vec::with_capacity(items.len());
|
|
1035
|
-
for item in items {
|
|
1036
|
-
outcomes.push(bound(param, item, instance, scope, predicate)?.truthy());
|
|
1037
|
-
}
|
|
1038
|
-
Ok(Value::Bool(match mode {
|
|
1039
|
-
BlockMode::All => outcomes.iter().all(|o| *o),
|
|
1040
|
-
BlockMode::Any => outcomes.iter().any(|o| *o),
|
|
1041
|
-
BlockMode::None => !outcomes.iter().any(|o| *o),
|
|
1042
|
-
}))
|
|
1043
|
-
}
|
|
1044
|
-
// The first accepted element ends the scan; a miss is nil, not an error.
|
|
1045
|
-
Expr::Find {
|
|
1046
|
-
receiver,
|
|
1047
|
-
param,
|
|
1048
|
-
predicate,
|
|
1049
|
-
path,
|
|
1050
|
-
} => {
|
|
1051
|
-
let items = match recur(receiver)? {
|
|
1052
|
-
Value::Array(items) => items,
|
|
1053
|
-
other => return Err(format!("find expects a list, got {}", describe(&other))),
|
|
1054
|
-
};
|
|
1055
|
-
for item in items {
|
|
1056
|
-
if bound(param, item.clone(), instance, scope, predicate)?.truthy() {
|
|
1057
|
-
return if path.is_empty() {
|
|
1058
|
-
Ok(unwrap_scalar(item))
|
|
1059
|
-
} else {
|
|
1060
|
-
Ok(unwrap_scalar(walk_path(item, path)?))
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
Ok(Value::Nil)
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
/// Evaluates a block's `predicate` with `param` bound to `item`, shadowing any same-named field.
|
|
1070
|
-
fn bound(
|
|
1071
|
-
param: &str,
|
|
1072
|
-
item: Value,
|
|
1073
|
-
instance: &Json,
|
|
1074
|
-
scope: Option<&Scope>,
|
|
1075
|
-
predicate: &Expr,
|
|
1076
|
-
) -> Result<Value, String> {
|
|
1077
|
-
let inner = Scope {
|
|
1078
|
-
name: param,
|
|
1079
|
-
value: item,
|
|
1080
|
-
outer: scope,
|
|
1081
|
-
};
|
|
1082
|
-
eval(predicate, instance, Some(&inner))
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
/// The `sign_test` a comparison's flags spell, for the error message: `positive?` is
|
|
1086
|
-
/// `!(< 0 || == 0)`, `negative?` is `< 0`, `zero?` is `== 0`.
|
|
1087
|
-
const fn sign_test_name(cmp: Comparison) -> &'static str {
|
|
1088
|
-
match (cmp.less_than, cmp.equal) {
|
|
1089
|
-
(true, true) => "positive?",
|
|
1090
|
-
(true, false) => "negative?",
|
|
1091
|
-
_ => "zero?",
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
/// `Resolver.unwrap_scalar`: a one-field value object reads as its one value.
|
|
1096
|
-
fn unwrap_scalar(v: Value) -> Value {
|
|
1097
|
-
let sole = match &v {
|
|
1098
|
-
Value::Object(Json::Object(fields)) if fields.len() == 1 => fields
|
|
1099
|
-
.values()
|
|
1100
|
-
.next()
|
|
1101
|
-
.and_then(|only| Value::from_json(only).ok()),
|
|
1102
|
-
_ => None,
|
|
1103
|
-
};
|
|
1104
|
-
sole.unwrap_or(v)
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
/// `Resolver.walk_path`: each segment indexes the current value. An object yields the field or
|
|
1108
|
-
/// nil; a string yields the segment when it is a substring (Ruby's `String#[]`), else nil; a
|
|
1109
|
-
/// list or integer cannot be indexed by name; nil, floats and booleans end the walk in nil.
|
|
1110
|
-
/// That last fallthrough is what makes `!flag.nil?` (`["flag", "nil?"]`) mean "does `flag` exist".
|
|
1111
|
-
fn walk_path(start: Value, segments: &[String]) -> Result<Value, String> {
|
|
1112
|
-
let mut current = start;
|
|
1113
|
-
for segment in segments {
|
|
1114
|
-
current = match ¤t {
|
|
1115
|
-
Value::Object(Json::Object(fields)) => match fields.get(segment) {
|
|
1116
|
-
Some(next) => Value::from_json(next)?,
|
|
1117
|
-
None => Value::Nil,
|
|
1118
|
-
},
|
|
1119
|
-
Value::Str(text) if text.contains(segment.as_str()) => Value::Str(segment.clone()),
|
|
1120
|
-
Value::Str(_) => Value::Nil,
|
|
1121
|
-
Value::Array(_) | Value::Int(_) => {
|
|
1122
|
-
return Err(format!(
|
|
1123
|
-
"cannot read {} from {}",
|
|
1124
|
-
ruby_inspect(segment),
|
|
1125
|
-
describe(¤t)
|
|
1126
|
-
))
|
|
1127
|
-
}
|
|
1128
|
-
_ => return Ok(Value::Nil),
|
|
1129
|
-
};
|
|
1130
|
-
}
|
|
1131
|
-
Ok(current)
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
/// Resolves a path as `Resolver#lookup`: the head is a block parameter (innermost first) or a
|
|
1135
|
-
/// field of `instance`, and must exist; later segments go through `walk_path`.
|
|
1136
|
-
fn lookup(path: &[String], instance: &Json, scope: Option<&Scope>) -> Result<Value, String> {
|
|
1137
|
-
let Some((head, rest)) = path.split_first() else {
|
|
1138
|
-
return Err(format!("lookup given an empty path (path {path:?})"));
|
|
1139
|
-
};
|
|
1140
|
-
let start = match scope.and_then(|s| s.get(head)) {
|
|
1141
|
-
Some(value) => value.clone(),
|
|
1142
|
-
None => Value::from_json(instance.get(head).ok_or_else(|| {
|
|
1143
|
-
format!(
|
|
1144
|
-
"cannot resolve {} — no such attribute or argument",
|
|
1145
|
-
ruby_inspect(head)
|
|
1146
|
-
)
|
|
1147
|
-
})?)?,
|
|
1148
|
-
};
|
|
1149
|
-
Ok(unwrap_scalar(walk_path(start, rest)?))
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
#[cfg(test)]
|
|
1153
|
-
mod tests {
|
|
1154
|
-
use super::*;
|
|
1155
|
-
|
|
1156
|
-
#[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
|
|
1157
|
-
fn field(name: &str, value: Json) -> Json {
|
|
1158
|
-
serde_json::json!({ name: value })
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
#[test]
|
|
1162
|
-
fn cents_gte_zero_holds_for_a_non_negative_amount() {
|
|
1163
|
-
// `cents >= 0`, the shape `AstJson.emit_predicate` builds.
|
|
1164
|
-
let ast = serde_json::json!({"op":"compare","cmp":{"less_than":true,"equal":false,"negated":true},
|
|
1165
|
-
"left":{"op":"lookup","path":["cents"]},"right":{"op":"int","value":0}});
|
|
1166
|
-
let expr = parse(&ast).expect("valid Expr JSON");
|
|
1167
|
-
|
|
1168
|
-
assert_eq!(
|
|
1169
|
-
interpret(&expr, &field("cents", serde_json::json!(1200))),
|
|
1170
|
-
Ok(Value::Bool(true))
|
|
1171
|
-
);
|
|
1172
|
-
assert_eq!(
|
|
1173
|
-
interpret(&expr, &field("cents", serde_json::json!(0))),
|
|
1174
|
-
Ok(Value::Bool(true))
|
|
1175
|
-
);
|
|
1176
|
-
assert_eq!(
|
|
1177
|
-
interpret(&expr, &field("cents", serde_json::json!(-1))),
|
|
1178
|
-
Ok(Value::Bool(false))
|
|
1179
|
-
);
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
#[test]
|
|
1183
|
-
fn a_compound_and_of_four_comparisons_matches_chess_square_bounds() {
|
|
1184
|
-
// `file >= 0 && file <= 7 && rank >= 0 && rank <= 7`, chess.bluebook's `Square`.
|
|
1185
|
-
fn cmp(
|
|
1186
|
-
less_than: bool,
|
|
1187
|
-
equal: bool,
|
|
1188
|
-
negated: bool,
|
|
1189
|
-
field: &str,
|
|
1190
|
-
n: i64,
|
|
1191
|
-
) -> serde_json::Value {
|
|
1192
|
-
serde_json::json!({"op":"compare","cmp":{"less_than":less_than,"equal":equal,"negated":negated},
|
|
1193
|
-
"left":{"op":"lookup","path":[field]},"right":{"op":"int","value":n}})
|
|
1194
|
-
}
|
|
1195
|
-
let ast = serde_json::json!({"op":"and","left":{"op":"and","left":{"op":"and",
|
|
1196
|
-
"left": cmp(true, false, true, "file", 0),
|
|
1197
|
-
"right": cmp(true, true, false, "file", 7)},
|
|
1198
|
-
"right": cmp(true, false, true, "rank", 0)},
|
|
1199
|
-
"right": cmp(true, true, false, "rank", 7)});
|
|
1200
|
-
let expr = parse(&ast).expect("valid Expr JSON");
|
|
1201
|
-
|
|
1202
|
-
let on_board = serde_json::json!({"file": 3, "rank": 7});
|
|
1203
|
-
let off_board = serde_json::json!({"file": 8, "rank": 0});
|
|
1204
|
-
assert_eq!(interpret(&expr, &on_board), Ok(Value::Bool(true)));
|
|
1205
|
-
assert_eq!(interpret(&expr, &off_board), Ok(Value::Bool(false)));
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
#[test]
|
|
1209
|
-
fn a_blank_string_fails_the_not_empty_to_s_pattern() {
|
|
1210
|
-
// `!value.to_s.empty?`, the most common real invariant shape.
|
|
1211
|
-
let ast = serde_json::json!({"op":"not","expr":{"op":"empty","receiver":{"op":"to_s","receiver":{"op":"lookup","path":["value"]}}}});
|
|
1212
|
-
let expr = parse(&ast).expect("valid Expr JSON");
|
|
1213
|
-
|
|
1214
|
-
assert_eq!(
|
|
1215
|
-
interpret(&expr, &field("value", serde_json::json!("Margherita"))),
|
|
1216
|
-
Ok(Value::Bool(true))
|
|
1217
|
-
);
|
|
1218
|
-
assert_eq!(
|
|
1219
|
-
interpret(&expr, &field("value", serde_json::json!(""))),
|
|
1220
|
-
Ok(Value::Bool(false))
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
#[test]
|
|
1225
|
-
fn sign_test_positive_matches_a_real_amount_invariant() {
|
|
1226
|
-
// `value.positive?` compiles to NOT(value < 0 or value == 0).
|
|
1227
|
-
let ast = serde_json::json!({"op":"sign_test","cmp":{"less_than":true,"equal":true,"negated":true},"receiver":{"op":"lookup","path":["value"]}});
|
|
1228
|
-
let expr = parse(&ast).expect("valid Expr JSON");
|
|
1229
|
-
|
|
1230
|
-
assert_eq!(
|
|
1231
|
-
interpret(&expr, &field("value", serde_json::json!(5))),
|
|
1232
|
-
Ok(Value::Bool(true))
|
|
1233
|
-
);
|
|
1234
|
-
assert_eq!(
|
|
1235
|
-
interpret(&expr, &field("value", serde_json::json!(0))),
|
|
1236
|
-
Ok(Value::Bool(false))
|
|
1237
|
-
);
|
|
1238
|
-
assert_eq!(
|
|
1239
|
-
interpret(&expr, &field("value", serde_json::json!(-5))),
|
|
1240
|
-
Ok(Value::Bool(false))
|
|
1241
|
-
);
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
#[test]
|
|
1245
|
-
fn int_and_float_compare_equal_across_kinds_the_same_way_the_kernel_does() {
|
|
1246
|
-
let ast = serde_json::json!({"op":"compare","cmp":{"less_than":false,"equal":true,"negated":false},
|
|
1247
|
-
"left":{"op":"lookup","path":["cents"]},"right":{"op":"float","value":3.0}});
|
|
1248
|
-
let expr = parse(&ast).expect("valid Expr JSON");
|
|
1249
|
-
|
|
1250
|
-
assert_eq!(
|
|
1251
|
-
interpret(&expr, &field("cents", serde_json::json!(3))),
|
|
1252
|
-
Ok(Value::Bool(true))
|
|
1253
|
-
);
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
#[test]
|
|
1257
|
-
fn an_unrecognized_op_refuses_to_parse_rather_than_defaulting() {
|
|
1258
|
-
let ast = serde_json::json!({"op":"frobnicate"});
|
|
1259
|
-
let err = parse(&ast).unwrap_err();
|
|
1260
|
-
assert!(err.contains("unrecognized"), "{err}");
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
// Expected values below are real Ruby results (`Resolver.resolve` / `Evaluator.call`); the
|
|
1264
|
-
// differential spec `spec/rust_host_expr_json_conformance_spec.rb` re-checks them live.
|
|
1265
|
-
use serde_json::json;
|
|
1266
|
-
|
|
1267
|
-
#[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
|
|
1268
|
-
fn run(ast: Json, instance: Json) -> Result<Value, String> {
|
|
1269
|
-
interpret(&parse(&ast).expect("valid Expr JSON"), &instance)
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
fn lookup_of(name: &str) -> Json {
|
|
1273
|
-
json!({"op":"lookup","path":[name]})
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
fn int(n: i64) -> Json {
|
|
1277
|
-
json!({"op":"int","value":n})
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
fn string(s: &str) -> Json {
|
|
1281
|
-
json!({"op":"str","value":s})
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
#[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
|
|
1285
|
-
fn call(op: &str, receiver: Json) -> Json {
|
|
1286
|
-
json!({"op":op,"receiver":receiver})
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
#[allow(clippy::needless_pass_by_value)] // reason: test helper called with inline literals
|
|
1290
|
-
fn eq(left: Json, right: Json) -> Json {
|
|
1291
|
-
json!({"op":"compare","cmp":{"less_than":false,"equal":true,"negated":false},"left":left,"right":right})
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
fn strs(items: &[&str]) -> Value {
|
|
1295
|
-
Value::Array(items.iter().map(|s| Value::Str(s.to_string())).collect())
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
#[test]
|
|
1299
|
-
fn add_sums_numbers_and_promotes_to_float() {
|
|
1300
|
-
let add = |l: Json, r: Json| json!({"op":"add","left":l,"right":r});
|
|
1301
|
-
assert_eq!(run(add(int(2), int(3)), json!({})), Ok(Value::Int(5)));
|
|
1302
|
-
assert_eq!(
|
|
1303
|
-
run(add(int(2), json!({"op":"float","value":1.5})), json!({})),
|
|
1304
|
-
Ok(Value::Float(3.5))
|
|
1305
|
-
);
|
|
1306
|
-
assert_eq!(
|
|
1307
|
-
run(add(lookup_of("a"), int(1)), json!({"a": 9})),
|
|
1308
|
-
Ok(Value::Int(10))
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
#[test]
|
|
1313
|
-
fn add_refuses_a_non_number_and_an_overflow_with_rubys_words() {
|
|
1314
|
-
let add = |l: Json, r: Json| json!({"op":"add","left":l,"right":r});
|
|
1315
|
-
assert_eq!(
|
|
1316
|
-
run(add(string("a"), int(1)), json!({})),
|
|
1317
|
-
Err("addition expects a number, got \"a\"".to_string())
|
|
1318
|
-
);
|
|
1319
|
-
assert_eq!(
|
|
1320
|
-
run(add(lookup_of("a"), int(1)), json!({"a": null})),
|
|
1321
|
-
Err("addition expects a number, got nil".to_string())
|
|
1322
|
-
);
|
|
1323
|
-
assert_eq!(
|
|
1324
|
-
run(add(int(1), lookup_of("a")), json!({"a": [1]})),
|
|
1325
|
-
Err("addition expects a number, got [1]".to_string())
|
|
1326
|
-
);
|
|
1327
|
-
assert_eq!(
|
|
1328
|
-
run(add(int(i64::MAX), int(1)), json!({})),
|
|
1329
|
-
Err(
|
|
1330
|
-
"addition overflowed: 9223372036854775807 + 1 does not fit in a 64-bit integer"
|
|
1331
|
-
.to_string()
|
|
1332
|
-
)
|
|
1333
|
-
);
|
|
1334
|
-
let huge = json!({"op":"float","value":1.7e308});
|
|
1335
|
-
assert_eq!(
|
|
1336
|
-
run(add(huge.clone(), huge), json!({})),
|
|
1337
|
-
Err("addition overflowed: 1.7e+308 + 1.7e+308 is not a finite number".to_string())
|
|
1338
|
-
);
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
#[test]
|
|
1342
|
-
fn sub_mul_div_follow_rubys_limits_and_floor_integer_division() {
|
|
1343
|
-
let op = |name: &str, l: Json, r: Json| json!({"op":name,"left":l,"right":r});
|
|
1344
|
-
assert_eq!(run(op("sub", int(5), int(2)), json!({})), Ok(Value::Int(3)));
|
|
1345
|
-
assert_eq!(
|
|
1346
|
-
run(
|
|
1347
|
-
op("mul", int(6), json!({"op":"float","value":0.5})),
|
|
1348
|
-
json!({})
|
|
1349
|
-
),
|
|
1350
|
-
Ok(Value::Float(3.0))
|
|
1351
|
-
);
|
|
1352
|
-
assert_eq!(
|
|
1353
|
-
run(op("div", int(-7), int(2)), json!({})),
|
|
1354
|
-
Ok(Value::Int(-4))
|
|
1355
|
-
);
|
|
1356
|
-
assert_eq!(
|
|
1357
|
-
run(op("div", int(7), int(-2)), json!({})),
|
|
1358
|
-
Ok(Value::Int(-4))
|
|
1359
|
-
);
|
|
1360
|
-
assert_eq!(
|
|
1361
|
-
run(op("div", int(7), int(0)), json!({})),
|
|
1362
|
-
Err("divided by 0".to_string())
|
|
1363
|
-
);
|
|
1364
|
-
assert_eq!(
|
|
1365
|
-
run(op("mul", int(i64::MAX), int(2)), json!({})),
|
|
1366
|
-
Err("multiplication overflowed: 9223372036854775807 * 2 does not fit in a 64-bit integer".to_string())
|
|
1367
|
-
);
|
|
1368
|
-
assert_eq!(
|
|
1369
|
-
run(op("div", int(i64::MIN), int(-1)), json!({})),
|
|
1370
|
-
Err(
|
|
1371
|
-
"division overflowed: -9223372036854775808 / -1 does not fit in a 64-bit integer"
|
|
1372
|
-
.to_string()
|
|
1373
|
-
)
|
|
1374
|
-
);
|
|
1375
|
-
assert_eq!(
|
|
1376
|
-
run(op("sub", lookup_of("a"), int(1)), json!({"a": null})),
|
|
1377
|
-
Err("subtraction expects a number, got nil".to_string())
|
|
1378
|
-
);
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
#[test]
|
|
1382
|
-
fn modulo_floors_like_ruby_for_integers_and_floats() {
|
|
1383
|
-
let modulo = |r: Json, d: Json| json!({"op":"modulo","receiver":r,"divisor":d});
|
|
1384
|
-
assert_eq!(run(modulo(int(-7), int(3)), json!({})), Ok(Value::Int(2)));
|
|
1385
|
-
assert_eq!(run(modulo(int(7), int(-3)), json!({})), Ok(Value::Int(-2)));
|
|
1386
|
-
assert_eq!(
|
|
1387
|
-
run(modulo(int(i64::MIN), int(-1)), json!({})),
|
|
1388
|
-
Ok(Value::Int(0))
|
|
1389
|
-
);
|
|
1390
|
-
assert_eq!(
|
|
1391
|
-
run(modulo(json!({"op":"float","value":5.5}), int(2)), json!({})),
|
|
1392
|
-
Ok(Value::Float(1.5))
|
|
1393
|
-
);
|
|
1394
|
-
assert_eq!(
|
|
1395
|
-
run(
|
|
1396
|
-
modulo(int(7), json!({"op":"float","value":-2.5})),
|
|
1397
|
-
json!({})
|
|
1398
|
-
),
|
|
1399
|
-
Ok(Value::Float(-0.5))
|
|
1400
|
-
);
|
|
1401
|
-
assert_eq!(
|
|
1402
|
-
run(
|
|
1403
|
-
modulo(json!({"op":"float","value":-1.0}), int(2)),
|
|
1404
|
-
json!({})
|
|
1405
|
-
),
|
|
1406
|
-
Ok(Value::Float(1.0))
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
#[test]
|
|
1411
|
-
fn modulo_refuses_a_zero_divisor_and_a_non_number() {
|
|
1412
|
-
let modulo = |r: Json, d: Json| json!({"op":"modulo","receiver":r,"divisor":d});
|
|
1413
|
-
assert_eq!(
|
|
1414
|
-
run(modulo(int(5), int(0)), json!({})),
|
|
1415
|
-
Err("divided by 0".to_string())
|
|
1416
|
-
);
|
|
1417
|
-
assert_eq!(
|
|
1418
|
-
run(modulo(int(5), json!({"op":"float","value":0.0})), json!({})),
|
|
1419
|
-
Err("divided by 0".to_string())
|
|
1420
|
-
);
|
|
1421
|
-
assert_eq!(
|
|
1422
|
-
run(modulo(string("a"), int(2)), json!({})),
|
|
1423
|
-
Err("modulo expects a number, got \"a\"".to_string())
|
|
1424
|
-
);
|
|
1425
|
-
assert_eq!(
|
|
1426
|
-
run(modulo(int(2), lookup_of("d")), json!({"d": null})),
|
|
1427
|
-
Err("modulo expects a number, got nil".to_string())
|
|
1428
|
-
);
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
#[test]
|
|
1432
|
-
fn include_tests_list_membership_with_numeric_coercion_and_substrings() {
|
|
1433
|
-
let include = |h: Json, n: Json| json!({"op":"include","haystack":h,"needle":n});
|
|
1434
|
-
assert_eq!(
|
|
1435
|
-
run(
|
|
1436
|
-
include(lookup_of("tags"), string("b")),
|
|
1437
|
-
json!({"tags": ["a", "b"]})
|
|
1438
|
-
),
|
|
1439
|
-
Ok(Value::Bool(true))
|
|
1440
|
-
);
|
|
1441
|
-
assert_eq!(
|
|
1442
|
-
run(
|
|
1443
|
-
include(lookup_of("tags"), string("z")),
|
|
1444
|
-
json!({"tags": ["a", "b"]})
|
|
1445
|
-
),
|
|
1446
|
-
Ok(Value::Bool(false))
|
|
1447
|
-
);
|
|
1448
|
-
assert_eq!(
|
|
1449
|
-
run(
|
|
1450
|
-
include(lookup_of("n"), json!({"op":"float","value":2.0})),
|
|
1451
|
-
json!({"n": [1, 2]})
|
|
1452
|
-
),
|
|
1453
|
-
Ok(Value::Bool(true))
|
|
1454
|
-
);
|
|
1455
|
-
assert_eq!(
|
|
1456
|
-
run(
|
|
1457
|
-
include(lookup_of("n"), lookup_of("m")),
|
|
1458
|
-
json!({"n": [1, null], "m": null})
|
|
1459
|
-
),
|
|
1460
|
-
Ok(Value::Bool(true))
|
|
1461
|
-
);
|
|
1462
|
-
assert_eq!(
|
|
1463
|
-
run(
|
|
1464
|
-
include(lookup_of("s"), string("ell")),
|
|
1465
|
-
json!({"s": "hello"})
|
|
1466
|
-
),
|
|
1467
|
-
Ok(Value::Bool(true))
|
|
1468
|
-
);
|
|
1469
|
-
assert_eq!(
|
|
1470
|
-
run(include(lookup_of("s"), string("")), json!({"s": "hello"})),
|
|
1471
|
-
Ok(Value::Bool(true))
|
|
1472
|
-
);
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
#[test]
|
|
1476
|
-
fn include_on_anything_but_a_list_or_string_is_false_and_a_string_needs_a_string() {
|
|
1477
|
-
let include = |h: Json, n: Json| json!({"op":"include","haystack":h,"needle":n});
|
|
1478
|
-
assert_eq!(
|
|
1479
|
-
run(include(lookup_of("s"), string("a")), json!({"s": null})),
|
|
1480
|
-
Ok(Value::Bool(false))
|
|
1481
|
-
);
|
|
1482
|
-
assert_eq!(
|
|
1483
|
-
run(include(lookup_of("s"), string("a")), json!({"s": 5})),
|
|
1484
|
-
Ok(Value::Bool(false))
|
|
1485
|
-
);
|
|
1486
|
-
assert_eq!(
|
|
1487
|
-
run(include(lookup_of("s"), int(1)), json!({"s": "abc"})),
|
|
1488
|
-
Err("no implicit conversion of Integer into String".to_string())
|
|
1489
|
-
);
|
|
1490
|
-
assert_eq!(
|
|
1491
|
-
run(
|
|
1492
|
-
include(lookup_of("s"), lookup_of("n")),
|
|
1493
|
-
json!({"s": "abc", "n": null})
|
|
1494
|
-
),
|
|
1495
|
-
Err("no implicit conversion of nil into String".to_string())
|
|
1496
|
-
);
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
#[test]
|
|
1500
|
-
fn array_literals_evaluate_each_element_and_compose_with_first_last_and_include() {
|
|
1501
|
-
let array = json!({"op":"array","elements":[int(1), json!({"op":"float","value":2.5}), string("a"), json!({"op":"nil"})]});
|
|
1502
|
-
assert_eq!(
|
|
1503
|
-
run(array.clone(), json!({})),
|
|
1504
|
-
Ok(Value::Array(vec![
|
|
1505
|
-
Value::Int(1),
|
|
1506
|
-
Value::Float(2.5),
|
|
1507
|
-
Value::Str("a".to_string()),
|
|
1508
|
-
Value::Nil
|
|
1509
|
-
]))
|
|
1510
|
-
);
|
|
1511
|
-
assert_eq!(
|
|
1512
|
-
run(call("first", array.clone()), json!({})),
|
|
1513
|
-
Ok(Value::Int(1))
|
|
1514
|
-
);
|
|
1515
|
-
assert_eq!(run(call("last", array), json!({})), Ok(Value::Nil));
|
|
1516
|
-
assert_eq!(
|
|
1517
|
-
run(json!({"op":"array","elements":[]}), json!({})),
|
|
1518
|
-
Ok(Value::Array(vec![]))
|
|
1519
|
-
);
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
#[test]
|
|
1523
|
-
fn matches_regex_reads_ruby_anchors_flags_and_ascii_classes() {
|
|
1524
|
-
let matches = |pattern: &str, flags: &str| json!({"op":"matches_regex","receiver":lookup_of("x"),"pattern":pattern,"flags":flags});
|
|
1525
|
-
assert_eq!(
|
|
1526
|
-
run(matches("^b$", ""), json!({"x": "a\nb"})),
|
|
1527
|
-
Ok(Value::Bool(true))
|
|
1528
|
-
);
|
|
1529
|
-
assert_eq!(
|
|
1530
|
-
run(matches("\\Ab\\z", ""), json!({"x": "a\nb"})),
|
|
1531
|
-
Ok(Value::Bool(false))
|
|
1532
|
-
);
|
|
1533
|
-
assert_eq!(
|
|
1534
|
-
run(matches("a.b", ""), json!({"x": "a\nb"})),
|
|
1535
|
-
Ok(Value::Bool(false))
|
|
1536
|
-
);
|
|
1537
|
-
assert_eq!(
|
|
1538
|
-
run(matches("a.b", "m"), json!({"x": "a\nb"})),
|
|
1539
|
-
Ok(Value::Bool(true))
|
|
1540
|
-
);
|
|
1541
|
-
assert_eq!(
|
|
1542
|
-
run(matches("A", "i"), json!({"x": "a"})),
|
|
1543
|
-
Ok(Value::Bool(true))
|
|
1544
|
-
);
|
|
1545
|
-
assert_eq!(
|
|
1546
|
-
run(matches("\\d", ""), json!({"x": "\u{663}"})),
|
|
1547
|
-
Ok(Value::Bool(false))
|
|
1548
|
-
);
|
|
1549
|
-
assert_eq!(
|
|
1550
|
-
run(matches("\\A[\\w.]+\\z", ""), json!({"x": "a.b_c"})),
|
|
1551
|
-
Ok(Value::Bool(true))
|
|
1552
|
-
);
|
|
1553
|
-
assert_eq!(
|
|
1554
|
-
run(matches("a b", "x"), json!({"x": "ab"})),
|
|
1555
|
-
Ok(Value::Bool(true))
|
|
1556
|
-
);
|
|
1557
|
-
assert_eq!(
|
|
1558
|
-
run(matches("\\Aa\\Z", ""), json!({"x": "a\n"})),
|
|
1559
|
-
Ok(Value::Bool(true))
|
|
1560
|
-
);
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
#[test]
|
|
1564
|
-
fn matches_regex_coerces_scalars_and_refuses_the_rest_by_class() {
|
|
1565
|
-
let matches = json!({"op":"matches_regex","receiver":lookup_of("x"),"pattern":"\\A[0-9.]+\\z","flags":""});
|
|
1566
|
-
assert_eq!(
|
|
1567
|
-
run(matches.clone(), json!({"x": 12})),
|
|
1568
|
-
Ok(Value::Bool(true))
|
|
1569
|
-
);
|
|
1570
|
-
assert_eq!(
|
|
1571
|
-
run(matches.clone(), json!({"x": 1.5})),
|
|
1572
|
-
Ok(Value::Bool(true))
|
|
1573
|
-
);
|
|
1574
|
-
assert_eq!(
|
|
1575
|
-
run(
|
|
1576
|
-
json!({"op":"matches_regex","receiver":lookup_of("x"),"pattern":"\\A\\z","flags":""}),
|
|
1577
|
-
json!({"x": null})
|
|
1578
|
-
),
|
|
1579
|
-
Ok(Value::Bool(true))
|
|
1580
|
-
);
|
|
1581
|
-
assert_eq!(
|
|
1582
|
-
run(matches.clone(), json!({"x": true})),
|
|
1583
|
-
Err("match? expects a scalar, got TrueClass".to_string())
|
|
1584
|
-
);
|
|
1585
|
-
assert_eq!(
|
|
1586
|
-
run(matches.clone(), json!({"x": false})),
|
|
1587
|
-
Err("match? expects a scalar, got FalseClass".to_string())
|
|
1588
|
-
);
|
|
1589
|
-
assert_eq!(
|
|
1590
|
-
run(matches, json!({"x": [1]})),
|
|
1591
|
-
Err("match? expects a scalar, got Array".to_string())
|
|
1592
|
-
);
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
#[test]
|
|
1596
|
-
fn matches_regex_refuses_an_invalid_pattern_by_name() {
|
|
1597
|
-
let err = run(
|
|
1598
|
-
json!({"op":"matches_regex","receiver":string("a"),"pattern":"(","flags":""}),
|
|
1599
|
-
json!({}),
|
|
1600
|
-
)
|
|
1601
|
-
.unwrap_err();
|
|
1602
|
-
assert!(
|
|
1603
|
-
err.starts_with("match? given an invalid pattern \"(\" — "),
|
|
1604
|
-
"{err}"
|
|
1605
|
-
);
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
#[test]
|
|
1609
|
-
fn block_predicates_aggregate_with_ruby_answers_for_an_empty_list() {
|
|
1610
|
-
let block = |mode: &str| {
|
|
1611
|
-
json!({"op":"block_predicate","mode":mode,"receiver":lookup_of("seats"),"param":"s",
|
|
1612
|
-
"predicate":eq(json!({"op":"lookup","path":["s","taken"]}), json!({"op":"bool","value":false}))})
|
|
1613
|
-
};
|
|
1614
|
-
let seats = json!({"seats": [{"n": 1, "taken": true}, {"n": 2, "taken": false}]});
|
|
1615
|
-
assert_eq!(run(block("any"), seats.clone()), Ok(Value::Bool(true)));
|
|
1616
|
-
assert_eq!(run(block("all"), seats.clone()), Ok(Value::Bool(false)));
|
|
1617
|
-
assert_eq!(run(block("none"), seats), Ok(Value::Bool(false)));
|
|
1618
|
-
let empty = json!({"seats": []});
|
|
1619
|
-
assert_eq!(run(block("all"), empty.clone()), Ok(Value::Bool(true)));
|
|
1620
|
-
assert_eq!(run(block("any"), empty.clone()), Ok(Value::Bool(false)));
|
|
1621
|
-
assert_eq!(run(block("none"), empty), Ok(Value::Bool(true)));
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
#[test]
|
|
1625
|
-
fn a_block_predicate_evaluates_every_element_so_a_later_error_still_surfaces() {
|
|
1626
|
-
// `all?` would stop at the first false in a short-circuit loop; Ruby maps first.
|
|
1627
|
-
let ast = json!({"op":"block_predicate","mode":"all","receiver":lookup_of("xs"),"param":"x",
|
|
1628
|
-
"predicate":json!({"op":"sign_test","cmp":{"less_than":true,"equal":true,"negated":true},"receiver":lookup_of("x")})});
|
|
1629
|
-
assert_eq!(
|
|
1630
|
-
run(ast, json!({"xs": [-1, "a"]})),
|
|
1631
|
-
Err("positive? expects a number, got \"a\"".to_string())
|
|
1632
|
-
);
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
#[test]
|
|
1636
|
-
fn a_block_predicate_over_a_non_list_refuses_with_the_mode_named() {
|
|
1637
|
-
for (mode, word) in [("all", "all?"), ("any", "any?"), ("none", "none?")] {
|
|
1638
|
-
let ast = json!({"op":"block_predicate","mode":mode,"receiver":lookup_of("x"),"param":"y","predicate":{"op":"bool","value":true}});
|
|
1639
|
-
assert_eq!(
|
|
1640
|
-
run(ast.clone(), json!({"x": 5})),
|
|
1641
|
-
Err(format!("{word} expects a list, got 5"))
|
|
1642
|
-
);
|
|
1643
|
-
assert_eq!(
|
|
1644
|
-
run(ast, json!({"x": null})),
|
|
1645
|
-
Err(format!("{word} expects a list, got nil"))
|
|
1646
|
-
);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
#[test]
|
|
1651
|
-
fn the_block_parameter_shadows_a_same_named_field_only_inside_the_block() {
|
|
1652
|
-
let ast = json!({"op":"block_predicate","mode":"any","receiver":lookup_of("xs"),"param":"n",
|
|
1653
|
-
"predicate":{"op":"and","left":eq(lookup_of("n"), int(2)),"right":eq(lookup_of("m"), int(9))}});
|
|
1654
|
-
assert_eq!(
|
|
1655
|
-
run(ast.clone(), json!({"xs": [1, 2], "m": 9, "n": 100})),
|
|
1656
|
-
Ok(Value::Bool(true))
|
|
1657
|
-
);
|
|
1658
|
-
assert_eq!(
|
|
1659
|
-
run(ast, json!({"xs": [1, 2], "m": 8})),
|
|
1660
|
-
Ok(Value::Bool(false))
|
|
1661
|
-
);
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
#[test]
|
|
1665
|
-
fn nested_blocks_see_the_outer_parameter() {
|
|
1666
|
-
let inner = json!({"op":"block_predicate","mode":"none","receiver":lookup_of("xs"),"param":"o",
|
|
1667
|
-
"predicate":eq(lookup_of("o"), json!({"op":"add","left":lookup_of("s"),"right":int(1)}))});
|
|
1668
|
-
let outer = json!({"op":"block_predicate","mode":"any","receiver":lookup_of("xs"),"param":"s","predicate":inner});
|
|
1669
|
-
// Every element has a successor except the largest, so `any?` holds.
|
|
1670
|
-
assert_eq!(run(outer, json!({"xs": [1, 2, 4]})), Ok(Value::Bool(true)));
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
#[test]
|
|
1674
|
-
fn an_unbound_name_in_a_block_refuses_as_ruby_does() {
|
|
1675
|
-
let ast = json!({"op":"block_predicate","mode":"any","receiver":lookup_of("xs"),"param":"x","predicate":lookup_of("ghost")});
|
|
1676
|
-
assert_eq!(
|
|
1677
|
-
run(ast, json!({"xs": [1]})),
|
|
1678
|
-
Err("cannot resolve \"ghost\" — no such attribute or argument".to_string())
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
#[test]
|
|
1683
|
-
fn find_answers_the_first_match_projected_through_its_path_and_nil_on_a_miss() {
|
|
1684
|
-
let find = |wanted: bool, path: &[&str]| {
|
|
1685
|
-
json!({"op":"find","receiver":lookup_of("legs"),"param":"l","path":path,
|
|
1686
|
-
"predicate":eq(json!({"op":"lookup","path":["l","open"]}), json!({"op":"bool","value":wanted}))})
|
|
1687
|
-
};
|
|
1688
|
-
let legs = json!({"legs": [{"to": "A", "open": false}, {"to": "B", "open": true}, {"to": "C", "open": true}]});
|
|
1689
|
-
assert_eq!(
|
|
1690
|
-
run(find(true, &["to"]), legs.clone()),
|
|
1691
|
-
Ok(Value::Str("B".to_string()))
|
|
1692
|
-
);
|
|
1693
|
-
assert_eq!(run(find(true, &["nope"]), legs.clone()), Ok(Value::Nil));
|
|
1694
|
-
assert!(matches!(run(find(true, &[]), legs), Ok(Value::Object(_))));
|
|
1695
|
-
assert_eq!(
|
|
1696
|
-
run(
|
|
1697
|
-
find(true, &["to"]),
|
|
1698
|
-
json!({"legs": [{"to": "A", "open": false}]})
|
|
1699
|
-
),
|
|
1700
|
-
Ok(Value::Nil)
|
|
1701
|
-
);
|
|
1702
|
-
assert_eq!(
|
|
1703
|
-
run(find(true, &["to"]), json!({"legs": []})),
|
|
1704
|
-
Ok(Value::Nil)
|
|
1705
|
-
);
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
#[test]
|
|
1709
|
-
fn find_stops_at_the_first_match_but_refuses_a_non_list() {
|
|
1710
|
-
// The second element would raise; `find` never reaches it, as Ruby's `Array#find`.
|
|
1711
|
-
let ast = json!({"op":"find","receiver":lookup_of("xs"),"param":"x","path":[],
|
|
1712
|
-
"predicate":json!({"op":"sign_test","cmp":{"less_than":true,"equal":true,"negated":true},"receiver":lookup_of("x")})});
|
|
1713
|
-
assert_eq!(run(ast.clone(), json!({"xs": [3, "a"]})), Ok(Value::Int(3)));
|
|
1714
|
-
assert_eq!(
|
|
1715
|
-
run(ast, json!({"xs": "abc"})),
|
|
1716
|
-
Err("find expects a list, got \"abc\"".to_string())
|
|
1717
|
-
);
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
#[test]
|
|
1721
|
-
fn presence_and_assignment_follow_blank_and_nil() {
|
|
1722
|
-
let presence =
|
|
1723
|
-
|negated: bool| json!({"op":"presence","receiver":lookup_of("x"),"negated":negated});
|
|
1724
|
-
let assignment =
|
|
1725
|
-
|negated: bool| json!({"op":"assignment","receiver":lookup_of("x"),"negated":negated});
|
|
1726
|
-
for (value, present) in [
|
|
1727
|
-
(json!(null), false),
|
|
1728
|
-
(json!(false), false),
|
|
1729
|
-
(json!(""), false),
|
|
1730
|
-
(json!([]), false),
|
|
1731
|
-
(json!(" "), true),
|
|
1732
|
-
(json!(0), true),
|
|
1733
|
-
(json!(true), true),
|
|
1734
|
-
(json!([0]), true),
|
|
1735
|
-
] {
|
|
1736
|
-
assert_eq!(
|
|
1737
|
-
run(presence(false), json!({"x": value.clone()})),
|
|
1738
|
-
Ok(Value::Bool(present)),
|
|
1739
|
-
"present? of {value}"
|
|
1740
|
-
);
|
|
1741
|
-
assert_eq!(
|
|
1742
|
-
run(presence(true), json!({"x": value.clone()})),
|
|
1743
|
-
Ok(Value::Bool(!present)),
|
|
1744
|
-
"blank? of {value}"
|
|
1745
|
-
);
|
|
1746
|
-
}
|
|
1747
|
-
// `set?` asks only `!nil?`: an assigned empty or false value is set.
|
|
1748
|
-
assert_eq!(
|
|
1749
|
-
run(assignment(false), json!({"x": ""})),
|
|
1750
|
-
Ok(Value::Bool(true))
|
|
1751
|
-
);
|
|
1752
|
-
assert_eq!(
|
|
1753
|
-
run(assignment(false), json!({"x": false})),
|
|
1754
|
-
Ok(Value::Bool(true))
|
|
1755
|
-
);
|
|
1756
|
-
assert_eq!(
|
|
1757
|
-
run(assignment(false), json!({"x": null})),
|
|
1758
|
-
Ok(Value::Bool(false))
|
|
1759
|
-
);
|
|
1760
|
-
assert_eq!(
|
|
1761
|
-
run(assignment(true), json!({"x": null})),
|
|
1762
|
-
Ok(Value::Bool(true))
|
|
1763
|
-
);
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
#[test]
|
|
1767
|
-
fn split_follows_ruby_for_awk_empty_and_literal_separators() {
|
|
1768
|
-
let split = |sep: &str| json!({"op":"split","receiver":lookup_of("x"),"separator":sep});
|
|
1769
|
-
assert_eq!(
|
|
1770
|
-
run(split(" "), json!({"x": " a b c "})),
|
|
1771
|
-
Ok(strs(&["a", "b", "c"]))
|
|
1772
|
-
);
|
|
1773
|
-
assert_eq!(run(split(""), json!({"x": "aé"})), Ok(strs(&["a", "é"])));
|
|
1774
|
-
assert_eq!(
|
|
1775
|
-
run(split(","), json!({"x": "a,b,,"})),
|
|
1776
|
-
Ok(strs(&["a", "b"]))
|
|
1777
|
-
);
|
|
1778
|
-
assert_eq!(run(split(","), json!({"x": ",a"})), Ok(strs(&["", "a"])));
|
|
1779
|
-
assert_eq!(run(split(","), json!({"x": ""})), Ok(strs(&[])));
|
|
1780
|
-
assert_eq!(
|
|
1781
|
-
run(split(","), json!({"x": 5})),
|
|
1782
|
-
Err("split expects a string, got 5".to_string())
|
|
1783
|
-
);
|
|
1784
|
-
assert_eq!(
|
|
1785
|
-
run(split(","), json!({"x": null})),
|
|
1786
|
-
Err("split expects a string, got nil".to_string())
|
|
1787
|
-
);
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
#[test]
|
|
1791
|
-
fn first_and_last_answer_nil_for_an_empty_list_and_refuse_a_non_list() {
|
|
1792
|
-
assert_eq!(
|
|
1793
|
-
run(call("first", lookup_of("x")), json!({"x": [7, 8]})),
|
|
1794
|
-
Ok(Value::Int(7))
|
|
1795
|
-
);
|
|
1796
|
-
assert_eq!(
|
|
1797
|
-
run(call("last", lookup_of("x")), json!({"x": [7, 8]})),
|
|
1798
|
-
Ok(Value::Int(8))
|
|
1799
|
-
);
|
|
1800
|
-
assert_eq!(
|
|
1801
|
-
run(call("first", lookup_of("x")), json!({"x": []})),
|
|
1802
|
-
Ok(Value::Nil)
|
|
1803
|
-
);
|
|
1804
|
-
assert_eq!(
|
|
1805
|
-
run(call("last", lookup_of("x")), json!({"x": "abc"})),
|
|
1806
|
-
Err("last expects a list, got \"abc\"".to_string())
|
|
1807
|
-
);
|
|
1808
|
-
assert_eq!(
|
|
1809
|
-
run(call("first", lookup_of("x")), json!({"x": null})),
|
|
1810
|
-
Err("first expects a list, got nil".to_string())
|
|
1811
|
-
);
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
#[test]
|
|
1815
|
-
fn strip_trims_rubys_whitespace_and_needs_a_string() {
|
|
1816
|
-
let strip_of = |side: &str, text: &str| {
|
|
1817
|
-
run(
|
|
1818
|
-
json!({"op":"strip","receiver":lookup_of("x"),"side":side}),
|
|
1819
|
-
json!({"x": text}),
|
|
1820
|
-
)
|
|
1821
|
-
};
|
|
1822
|
-
assert_eq!(strip_of("both", " \t a b \0"), Ok(Value::Str("a b".into())));
|
|
1823
|
-
assert_eq!(strip_of("left", " a "), Ok(Value::Str("a ".into())));
|
|
1824
|
-
assert_eq!(strip_of("right", " a "), Ok(Value::Str(" a".into())));
|
|
1825
|
-
assert_eq!(
|
|
1826
|
-
strip_of("both", "\u{a0}a"),
|
|
1827
|
-
Ok(Value::Str("\u{a0}a".into()))
|
|
1828
|
-
);
|
|
1829
|
-
assert_eq!(
|
|
1830
|
-
run(
|
|
1831
|
-
json!({"op":"strip","receiver":lookup_of("x"),"side":"both"}),
|
|
1832
|
-
json!({"x": null})
|
|
1833
|
-
),
|
|
1834
|
-
Err("strip expects a string, got nil".to_string())
|
|
1835
|
-
);
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
#[test]
|
|
1839
|
-
fn starts_with_and_ends_with_need_a_string() {
|
|
1840
|
-
let starts = json!({"op":"starts_with","receiver":lookup_of("x"),"substring":"ab"});
|
|
1841
|
-
let ends = json!({"op":"ends_with","receiver":lookup_of("x"),"substring":"bc"});
|
|
1842
|
-
assert_eq!(
|
|
1843
|
-
run(starts.clone(), json!({"x": "abc"})),
|
|
1844
|
-
Ok(Value::Bool(true))
|
|
1845
|
-
);
|
|
1846
|
-
assert_eq!(
|
|
1847
|
-
run(ends.clone(), json!({"x": "abc"})),
|
|
1848
|
-
Ok(Value::Bool(true))
|
|
1849
|
-
);
|
|
1850
|
-
assert_eq!(run(ends, json!({"x": "abd"})), Ok(Value::Bool(false)));
|
|
1851
|
-
assert_eq!(
|
|
1852
|
-
run(starts.clone(), json!({"x": null})),
|
|
1853
|
-
Err("start_with? expects a string, got nil".to_string())
|
|
1854
|
-
);
|
|
1855
|
-
assert_eq!(
|
|
1856
|
-
run(starts, json!({"x": [1]})),
|
|
1857
|
-
Err("start_with? expects a string, got [1]".to_string())
|
|
1858
|
-
);
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
#[test]
|
|
1862
|
-
fn a_path_through_a_string_list_or_integer_walks_like_ruby() {
|
|
1863
|
-
let path = |segments: &[&str]| json!({"op":"lookup","path":segments});
|
|
1864
|
-
// `String#[]` answers the segment when it is a substring.
|
|
1865
|
-
assert_eq!(
|
|
1866
|
-
run(path(&["x", "foo"]), json!({"x": "abcfoo"})),
|
|
1867
|
-
Ok(Value::Str("foo".to_string()))
|
|
1868
|
-
);
|
|
1869
|
-
assert_eq!(
|
|
1870
|
-
run(path(&["x", "nil?"]), json!({"x": "abc"})),
|
|
1871
|
-
Ok(Value::Nil)
|
|
1872
|
-
);
|
|
1873
|
-
assert_eq!(run(path(&["x", "a"]), json!({"x": 1.5})), Ok(Value::Nil));
|
|
1874
|
-
assert_eq!(run(path(&["x", "a"]), json!({"x": true})), Ok(Value::Nil));
|
|
1875
|
-
assert_eq!(
|
|
1876
|
-
run(path(&["x", "a"]), json!({"x": [1]})),
|
|
1877
|
-
Err("cannot read \"a\" from [1]".to_string())
|
|
1878
|
-
);
|
|
1879
|
-
assert_eq!(
|
|
1880
|
-
run(path(&["x", "a"]), json!({"x": 5})),
|
|
1881
|
-
Err("cannot read \"a\" from 5".to_string())
|
|
1882
|
-
);
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
#[test]
|
|
1886
|
-
fn a_one_field_object_reads_as_its_value_and_a_wider_one_stays_an_object() {
|
|
1887
|
-
assert_eq!(
|
|
1888
|
-
run(lookup_of("x"), json!({"x": {"value": 7}})),
|
|
1889
|
-
Ok(Value::Int(7))
|
|
1890
|
-
);
|
|
1891
|
-
assert!(matches!(
|
|
1892
|
-
run(lookup_of("x"), json!({"x": {"a": 1, "b": 2}})),
|
|
1893
|
-
Ok(Value::Object(_))
|
|
1894
|
-
));
|
|
1895
|
-
assert_eq!(
|
|
1896
|
-
run(
|
|
1897
|
-
json!({"op":"lookup","path":["x","b"]}),
|
|
1898
|
-
json!({"x": {"a": 1, "b": 2}})
|
|
1899
|
-
),
|
|
1900
|
-
Ok(Value::Int(2))
|
|
1901
|
-
);
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
#[test]
|
|
1905
|
-
fn errors_are_worded_as_rubys_describe() {
|
|
1906
|
-
assert_eq!(
|
|
1907
|
-
run(call("size", lookup_of("x")), json!({"x": 5})),
|
|
1908
|
-
Err("size expects a list or string, got 5".to_string())
|
|
1909
|
-
);
|
|
1910
|
-
assert_eq!(
|
|
1911
|
-
run(call("empty", lookup_of("x")), json!({"x": null})),
|
|
1912
|
-
Err("empty? expects a list or string, got nil".to_string())
|
|
1913
|
-
);
|
|
1914
|
-
assert_eq!(
|
|
1915
|
-
run(call("to_s", lookup_of("x")), json!({"x": [1, "a"]})),
|
|
1916
|
-
Err("to_s expects a scalar, got [1,\"a\"]".to_string())
|
|
1917
|
-
);
|
|
1918
|
-
assert_eq!(
|
|
1919
|
-
run(
|
|
1920
|
-
json!({"op":"compare","cmp":{"less_than":true,"equal":false,"negated":false},"left":lookup_of("x"),"right":int(1)}),
|
|
1921
|
-
json!({"x": "a"})
|
|
1922
|
-
),
|
|
1923
|
-
Err("comparison of String with 1 failed".to_string())
|
|
1924
|
-
);
|
|
1925
|
-
assert_eq!(
|
|
1926
|
-
run(
|
|
1927
|
-
json!({"op":"sign_test","cmp":{"less_than":false,"equal":true,"negated":false},"receiver":lookup_of("x")}),
|
|
1928
|
-
json!({"x": "a"})
|
|
1929
|
-
),
|
|
1930
|
-
Err("zero? expects a number, got \"a\"".to_string())
|
|
1931
|
-
);
|
|
1932
|
-
assert_eq!(
|
|
1933
|
-
run(lookup_of("ghost"), json!({})),
|
|
1934
|
-
Err("cannot resolve \"ghost\" — no such attribute or argument".to_string())
|
|
1935
|
-
);
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
#[test]
|
|
1939
|
-
fn floats_print_as_ruby_prints_them() {
|
|
1940
|
-
for (f, text) in [
|
|
1941
|
-
(3.0, "3.0"),
|
|
1942
|
-
(0.5, "0.5"),
|
|
1943
|
-
(-0.0, "-0.0"),
|
|
1944
|
-
(100_000_000_000_000.0, "100000000000000.0"),
|
|
1945
|
-
(999_999_999_999_999.9, "999999999999999.9"),
|
|
1946
|
-
(1e15, "1.0e+15"),
|
|
1947
|
-
(1.5e15, "1.5e+15"),
|
|
1948
|
-
(1e20, "1.0e+20"),
|
|
1949
|
-
(0.0001, "0.0001"),
|
|
1950
|
-
(0.00001, "1.0e-05"),
|
|
1951
|
-
(1.5e-7, "1.5e-07"),
|
|
1952
|
-
(123_456_789.123_456_79, "123456789.12345679"),
|
|
1953
|
-
] {
|
|
1954
|
-
assert_eq!(ruby_float(f), text);
|
|
1955
|
-
assert_eq!(
|
|
1956
|
-
run(call("to_s", lookup_of("x")), json!({"x": f})),
|
|
1957
|
-
Ok(Value::Str(text.to_string()))
|
|
1958
|
-
);
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
#[test]
|
|
1963
|
-
fn strings_inspect_as_ruby_inspects_them() {
|
|
1964
|
-
assert_eq!(
|
|
1965
|
-
ruby_inspect("a\"b\\c\n\u{1}#{x}\u{7f}é"),
|
|
1966
|
-
"\"a\\\"b\\\\c\\n\\u0001\\#{x}\\u007Fé\""
|
|
1967
|
-
);
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
#[test]
|
|
1971
|
-
fn equality_compares_nested_numbers_across_kinds_like_ruby() {
|
|
1972
|
-
assert!(values_equal(
|
|
1973
|
-
&Value::Array(vec![Value::Int(1)]),
|
|
1974
|
-
&Value::Array(vec![Value::Float(1.0)])
|
|
1975
|
-
));
|
|
1976
|
-
assert!(!values_equal(
|
|
1977
|
-
&Value::Int(9_007_199_254_740_993),
|
|
1978
|
-
&Value::Float(9_007_199_254_740_992.0)
|
|
1979
|
-
));
|
|
1980
|
-
assert!(values_equal(&Value::Int(3), &Value::Float(3.0)));
|
|
1981
|
-
assert!(!values_equal(&Value::Int(3), &Value::Float(3.5)));
|
|
1982
|
-
}
|
|
1983
|
-
}
|