hecks 3.10.0 → 3.12.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 +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +158 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +70 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/newcomer.rb +50 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +4 -15
- data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +33 -7
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +53 -18
- data/lib/hecks/hecks/hecks.world +13 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.behaviors +19 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projection_files/expression_tables.rb +7 -2
- data/lib/hecks/projections/deploy/box/compose.rb +6 -4
- data/lib/hecks/projections/deploy/box/edge.rb +62 -0
- data/lib/hecks/projections/deploy/box/logging.rb +99 -0
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
- data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
- data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
- data/lib/hecks/projections/deploy/box/settings.rb +6 -2
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
- data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
- data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
- data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box.rb +15 -3
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
- data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +49 -23
- data/lib/hecks/projections/site/role_probe.rb +2 -0
- data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +22 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/usage.rb +15 -4
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/host.rb +11 -4
- data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +1 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/ci_gate_decision.rb +35 -0
- data/lib/hecks/tools/ci_gates.rb +20 -1
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/files.rb +8 -0
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/lanes.rb +1 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/release_only.rb +111 -0
- data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +19 -5
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
- data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
- data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +6 -0
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +2 -0
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +1 -0
- data/rust/codegen/src/mutations.rs +128 -3
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/extension.rs +13 -1
- data/rust/host/src/lib.rs +1 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
- data/rust/host/src/platform_signin/config.rs +512 -0
- data/rust/host/src/platform_signin/cookie.rs +237 -0
- data/rust/host/src/platform_signin/extension.rs +190 -0
- data/rust/host/src/platform_signin/fetcher.rs +322 -0
- data/rust/host/src/platform_signin/handler.rs +251 -0
- data/rust/host/src/platform_signin/handler_tests.rs +554 -0
- data/rust/host/src/platform_signin/jti.rs +196 -0
- data/rust/host/src/platform_signin/jwks.rs +542 -0
- data/rust/host/src/platform_signin/jws.rs +249 -0
- data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
- data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
- data/rust/host/src/platform_signin/mod.rs +75 -0
- data/rust/host/src/platform_signin/mode_tests.rs +382 -0
- data/rust/host/src/platform_signin/pages.rs +204 -0
- data/rust/host/src/platform_signin/query.rs +233 -0
- data/rust/host/src/platform_signin/session.rs +151 -0
- data/rust/host/src/platform_signin/settings.rs +385 -0
- data/rust/host/src/platform_signin/strict_json.rs +351 -0
- data/rust/host/src/platform_signin/test_support.rs +235 -0
- data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
- data/rust/host/src/platform_signin/verify.rs +604 -0
- data/rust/host/src/platform_signin/wiring_support.rs +319 -0
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +53 -9
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +34 -17
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +66 -3
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +178 -38
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
- data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
- data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
- data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//! The `Policy` construct: `on`/`trigger` fill `on_event`/`trigger_command`, `
|
|
2
|
-
//! target domain.
|
|
1
|
+
//! The `Policy` construct: `on`/`trigger` fill `on_event`/`trigger_command`, `ask` names a need
|
|
2
|
+
//! the hecksagon maps to a port, `across` names the target domain.
|
|
3
3
|
|
|
4
4
|
use crate::canonical;
|
|
5
5
|
use crate::diag::{Diagnostic, ParseResult};
|
|
@@ -49,6 +49,17 @@ pub fn parse_body(
|
|
|
49
49
|
// Shares `dispatch`'s `with:` parser so the two cannot drift.
|
|
50
50
|
policy.with_spec = super::process_manager::parse_with_pairs_opt(&gated.args);
|
|
51
51
|
}
|
|
52
|
+
// Same `with:` projection as `trigger`; which port answers is the hecksagon's to say.
|
|
53
|
+
"ask" => {
|
|
54
|
+
policy.ask = Some(super::positional_symbol(
|
|
55
|
+
file,
|
|
56
|
+
gated.line.number,
|
|
57
|
+
"ask",
|
|
58
|
+
&gated.args,
|
|
59
|
+
1,
|
|
60
|
+
)?);
|
|
61
|
+
policy.with_spec = super::process_manager::parse_with_pairs_opt(&gated.args);
|
|
62
|
+
}
|
|
52
63
|
"across" => {
|
|
53
64
|
policy.target_domain = Some(super::positional_text(
|
|
54
65
|
file,
|
|
@@ -64,17 +64,21 @@ fn try_reference_named_invariant(
|
|
|
64
64
|
Ok(Some(resolved))
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/// Parses one `value_object` body. Returns the value object and the closed sets its type-position
|
|
68
|
+
/// `one_of(...)` attributes synthesized, which the owning aggregate installs right after it, as
|
|
69
|
+
/// `AggregateBuilder#value_object` does with `builder.closed_sets`.
|
|
67
70
|
pub fn parse_body(
|
|
68
71
|
file: &str,
|
|
69
72
|
lines: &[SourceLine],
|
|
70
73
|
pos: &mut usize,
|
|
71
74
|
name: &str,
|
|
72
75
|
owner_value_objects: &[ir::ValueObject],
|
|
73
|
-
) -> ParseResult<ir::ValueObject> {
|
|
76
|
+
) -> ParseResult<(ir::ValueObject, Vec<ir::ValueObject>)> {
|
|
74
77
|
let mut vo = ir::ValueObject {
|
|
75
78
|
name: name.to_string(),
|
|
76
79
|
..Default::default()
|
|
77
80
|
};
|
|
81
|
+
let mut synthesized: Vec<ir::ValueObject> = Vec::new();
|
|
78
82
|
|
|
79
83
|
loop {
|
|
80
84
|
if let Some(invariant) =
|
|
@@ -87,14 +91,15 @@ pub fn parse_body(
|
|
|
87
91
|
let Some(gated) = super::next_line(file, lines, pos, "ValueObject")? else {
|
|
88
92
|
// Mirrors `ValueObjectBuilder#build`: a non-empty `members` closes the set.
|
|
89
93
|
vo.closed_set = vo.closed_set || !vo.members.is_empty();
|
|
90
|
-
return Ok(vo);
|
|
94
|
+
return Ok((vo, synthesized));
|
|
91
95
|
};
|
|
92
96
|
|
|
93
97
|
match gated.row.word {
|
|
94
|
-
// An inline type-position `one_of(...)` set is
|
|
95
|
-
// `ValueObject` has no nested-value-objects field.
|
|
98
|
+
// An inline type-position `one_of(...)` set is returned beside the value object (see
|
|
99
|
+
// build/closed_sets.rs); `ValueObject` has no nested-value-objects field.
|
|
96
100
|
"attribute" => {
|
|
97
|
-
let (attribute, one_of) = build_value_object_attribute(file, &gated.args)?;
|
|
101
|
+
let (attribute, one_of, closed_set) = build_value_object_attribute(file, &gated.args)?;
|
|
102
|
+
synthesized.extend(closed_set);
|
|
98
103
|
if let Some(values) = one_of {
|
|
99
104
|
let vo_name = vo.name.clone();
|
|
100
105
|
install_inline_closed_set(
|
|
@@ -135,8 +140,8 @@ pub fn parse_body(
|
|
|
135
140
|
fn build_value_object_attribute(
|
|
136
141
|
file: &str,
|
|
137
142
|
args: &super::ArgumentGateResult,
|
|
138
|
-
) -> ParseResult<(ir::Attribute, Option<Vec<
|
|
139
|
-
let (attribute,
|
|
143
|
+
) -> ParseResult<(ir::Attribute, Option<Vec<ruby_value::Value>>, Option<ir::ValueObject>)> {
|
|
144
|
+
let (attribute, closed_set) = super::build_attribute(file, 0, "attribute", args)?;
|
|
140
145
|
let one_of = super::named_raw(args, "one_of").map(|raw| {
|
|
141
146
|
let trimmed = raw.trim();
|
|
142
147
|
let inner = trimmed
|
|
@@ -145,10 +150,10 @@ fn build_value_object_attribute(
|
|
|
145
150
|
.unwrap_or(trimmed);
|
|
146
151
|
ruby_value::split_items(inner)
|
|
147
152
|
.into_iter()
|
|
148
|
-
.map(|segment|
|
|
153
|
+
.map(|segment| cell_value(segment.trim()))
|
|
149
154
|
.collect()
|
|
150
155
|
});
|
|
151
|
-
Ok((attribute, one_of))
|
|
156
|
+
Ok((attribute, one_of, closed_set))
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
/// Installs `attribute :name, String, one_of: [...]`; refused unless it is the value object's
|
|
@@ -159,7 +164,7 @@ fn install_inline_closed_set(
|
|
|
159
164
|
vo_name: &str,
|
|
160
165
|
vo: &mut ir::ValueObject,
|
|
161
166
|
field: &str,
|
|
162
|
-
values: &[
|
|
167
|
+
values: &[ruby_value::Value],
|
|
163
168
|
) -> ParseResult<()> {
|
|
164
169
|
if !vo.attributes.is_empty() {
|
|
165
170
|
return Err(Diagnostic::new(
|
|
@@ -177,11 +182,11 @@ fn install_inline_closed_set(
|
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
vo.closed_set = true;
|
|
180
|
-
//
|
|
181
|
-
// `
|
|
185
|
+
// Read like a `member` row (`push_member`): a quoted value stays a String and a bare
|
|
186
|
+
// `true`, `false` or number keeps its type, as Ruby's builder holds them.
|
|
182
187
|
vo.members = values
|
|
183
188
|
.iter()
|
|
184
|
-
.map(|value| vec![(field.to_string(),
|
|
189
|
+
.map(|value| vec![(field.to_string(), value.clone())])
|
|
185
190
|
.collect();
|
|
186
191
|
Ok(())
|
|
187
192
|
}
|
|
@@ -202,15 +207,27 @@ fn push_member(
|
|
|
202
207
|
let fields = args
|
|
203
208
|
.named
|
|
204
209
|
.iter()
|
|
205
|
-
.map(|(field, raw)| (field.clone(),
|
|
210
|
+
.map(|(field, raw)| (field.clone(), cell_value(raw)))
|
|
206
211
|
.collect();
|
|
207
212
|
members.push(fields);
|
|
208
213
|
Ok(())
|
|
209
214
|
}
|
|
210
215
|
|
|
211
|
-
///
|
|
212
|
-
///
|
|
213
|
-
///
|
|
216
|
+
/// Reads one member cell from its source spelling, as `Scalars.unmark` reads a stored one
|
|
217
|
+
/// (lib/hecks/bluebook/assembly/scalars.rb): a quoted cell is a String whatever it spells, and a
|
|
218
|
+
/// bare boolean or number keeps its type. Anything else is read through its text.
|
|
219
|
+
fn cell_value(raw: &str) -> ruby_value::Value {
|
|
220
|
+
match ruby_value::read(raw) {
|
|
221
|
+
held @ (ruby_value::Value::Str(_)
|
|
222
|
+
| ruby_value::Value::Bool(_)
|
|
223
|
+
| ruby_value::Value::Int(_)
|
|
224
|
+
| ruby_value::Value::Float(_)) => held,
|
|
225
|
+
other => unmark_scalar(&ruby_value::to_s(&other)),
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// The text-shape half of `Scalars.unmark`: for a cell that is neither a quoted String nor a
|
|
230
|
+
/// bare boolean or number, `true`, `false` and number-shaped text read back typed.
|
|
214
231
|
fn unmark_scalar(text: &str) -> ruby_value::Value {
|
|
215
232
|
if text == "true" {
|
|
216
233
|
return ruby_value::Value::Bool(true);
|
|
@@ -41,7 +41,7 @@ pub fn render(value: &Value) -> String {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/// Ruby's `Float#to_s` always keeps a digit after the point (`1.0`); Rust prints `1`.
|
|
44
|
-
fn format_ruby_float(value: f64) -> String {
|
|
44
|
+
pub(crate) fn format_ruby_float(value: f64) -> String {
|
|
45
45
|
let text = format!("{value}");
|
|
46
46
|
if text.contains('.') || text.contains('e') || text.contains("inf") || text.contains("NaN") {
|
|
47
47
|
text
|
|
@@ -68,7 +68,7 @@ impl crate::kernel::ToJson for TmplRecord {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
impl crate::kernel::SetProjectedField for TmplRecord {
|
|
71
|
-
fn set_projected_field(&mut self, name: &'static str, value: Option<
|
|
71
|
+
fn set_projected_field(&mut self, name: &'static str, value: Option<crate::kernel::Value>) {
|
|
72
72
|
let _ = (name, value);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -94,7 +94,7 @@ fn tmpl_hydrate_placeholder() -> crate::kernel::Hydrate<'static, TmplRecord> {
|
|
|
94
94
|
|
|
95
95
|
fn tmpl_mutation_lines_placeholder(record: &mut TmplRecord) {}
|
|
96
96
|
|
|
97
|
-
fn tmpl_seed_projections_placeholder() -> Vec<(&'static str, Option<
|
|
97
|
+
fn tmpl_seed_projections_placeholder() -> Vec<(&'static str, Option<crate::kernel::Value>)> {
|
|
98
98
|
Vec::new()
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -160,9 +160,9 @@ fn tmpl_emit_placeholder() -> &'static str {
|
|
|
160
160
|
""
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
// `delegates_to "Entity.Command"`: the
|
|
163
|
+
// `delegates_to "Entity.Command"`: the entry point runs `apply_entity_command` inside its closure.
|
|
164
164
|
// `delegate_prelude` fills the prelude slot; `delegate_apply` fills the mutation slot.
|
|
165
|
-
// The inner `|record|` deliberately shadows the
|
|
165
|
+
// The inner `|record|` deliberately shadows the entry point's: entity `sets` only reach the element.
|
|
166
166
|
fn tmpl_aliases_placeholder() -> (&'static str, &'static str) {
|
|
167
167
|
("", "")
|
|
168
168
|
}
|
|
@@ -12,7 +12,7 @@ fn tmpl_admits_check_host(tmpl_scalar: String) -> Result<(), crate::kernel::Refu
|
|
|
12
12
|
|
|
13
13
|
fn tmpl_pattern_check_host(tmpl_scalar: String) -> Result<(), crate::kernel::Refusal> {
|
|
14
14
|
// TMPL:pattern_check BEGIN
|
|
15
|
-
if !crate::kernel::pattern::
|
|
15
|
+
if !crate::kernel::pattern::matches_whole("tmpl_pattern_text", &tmpl_scalar) { return Err(crate::kernel::Refusal::TypeMismatch(crate::kernel::refusal_wording::TypeMismatchPatternMismatchArgs { r#type: "tmpl_pattern_owner", field: "tmpl_pattern_field", pattern: "tmpl_pattern_text", offered: format!("{:?}", tmpl_scalar).as_str() }.render_args())); }
|
|
16
16
|
// TMPL:pattern_check END
|
|
17
17
|
Ok(())
|
|
18
18
|
}
|
data/rust/src/exemplar/json.rs
CHANGED
|
@@ -17,6 +17,20 @@ impl TmplKind {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
pub fn from_json(v: &crate::kernel::Json) -> Result<Self, crate::kernel::Refusal> {
|
|
20
|
+
// A name the set does not declare refuses first, as `Value::FieldChecks#check_unknown_fields`
|
|
21
|
+
// does in `validate!`: before a missing field, and before membership.
|
|
22
|
+
let unknown = v.unknown_keys(&["tmpl_field_name"]);
|
|
23
|
+
if !unknown.is_empty() {
|
|
24
|
+
let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();
|
|
25
|
+
return Err(crate::kernel::Refusal::UnknownArgument(
|
|
26
|
+
crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
|
|
27
|
+
command: "tmpl_closed_set_type",
|
|
28
|
+
unknown: &unknown,
|
|
29
|
+
declared: &["tmpl_field_name"],
|
|
30
|
+
}
|
|
31
|
+
.render_args(),
|
|
32
|
+
));
|
|
33
|
+
}
|
|
20
34
|
// A `one_of` closed set is admission-checked on the raw offered
|
|
21
35
|
// value, no shape check first — `Value::Admission#admit_member`
|
|
22
36
|
// runs on whatever `Value::Coercion#fields_for` auto-wrapped into
|
|
@@ -93,6 +107,10 @@ struct TmplTableRow {
|
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
const TMPL_TABLE: &[TmplTableRow] = &[];
|
|
110
|
+
const TMPL_DECLARED_NAMES: [&str; 0] = [];
|
|
111
|
+
const TMPL_ADMITTED: [&str; 0] = [];
|
|
112
|
+
|
|
113
|
+
fn tmpl_absent_field_checks() {}
|
|
96
114
|
|
|
97
115
|
// Function-call placeholders; the marker sits flush left because plain `render` never reindents.
|
|
98
116
|
fn tmpl_to_json_fields_block() -> (String, crate::kernel::Json) {
|
|
@@ -112,12 +130,36 @@ tmpl_to_json_fields_block()
|
|
|
112
130
|
}
|
|
113
131
|
|
|
114
132
|
pub fn from_json(v: &crate::kernel::Json) -> Result<Self, crate::kernel::Refusal> {
|
|
133
|
+
// `validate!`'s order: a name the set does not declare, then a field left out, then
|
|
134
|
+
// membership of the whole row.
|
|
135
|
+
let unknown = v.unknown_keys(&TMPL_DECLARED_NAMES);
|
|
136
|
+
if !unknown.is_empty() {
|
|
137
|
+
let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();
|
|
138
|
+
return Err(crate::kernel::Refusal::UnknownArgument(
|
|
139
|
+
crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
|
|
140
|
+
command: "tmpl_table_type",
|
|
141
|
+
unknown: &unknown,
|
|
142
|
+
declared: &TMPL_DECLARED_NAMES,
|
|
143
|
+
}
|
|
144
|
+
.render_args(),
|
|
145
|
+
));
|
|
146
|
+
}
|
|
147
|
+
tmpl_absent_field_checks();
|
|
115
148
|
for row in TMPL_TABLE {
|
|
116
149
|
if tmpl_from_json_conditions() {
|
|
117
150
|
return Ok(row.clone());
|
|
118
151
|
}
|
|
119
152
|
}
|
|
120
|
-
|
|
153
|
+
// The refusal quotes the first column only, as Ruby's `refuse_non_member!` does.
|
|
154
|
+
let offered = v.get("tmpl_first_field").map(|offered| offered.inspect()).unwrap_or_else(|| "nil".to_string());
|
|
155
|
+
Err(crate::kernel::Refusal::InvariantViolation(
|
|
156
|
+
crate::kernel::refusal_wording::InvariantViolationClosedSetMemberArgs {
|
|
157
|
+
r#type: "tmpl_table_type",
|
|
158
|
+
admitted: &TMPL_ADMITTED,
|
|
159
|
+
offered: offered.as_str(),
|
|
160
|
+
}
|
|
161
|
+
.render_args(),
|
|
162
|
+
))
|
|
121
163
|
}
|
|
122
164
|
}
|
|
123
165
|
// TMPL:closed_set_table_codec END
|
data/rust/src/kernel/cli.rs
CHANGED
|
@@ -162,15 +162,17 @@ pub fn run(input: &str) -> String {
|
|
|
162
162
|
refusals.push((question.clone(), refusal));
|
|
163
163
|
}
|
|
164
164
|
},
|
|
165
|
+
// Only a query the generator did not emit reaches here; the codegen manifest lists
|
|
166
|
+
// each one it skipped, with the reason.
|
|
165
167
|
None => refusals.push((
|
|
166
168
|
question.clone(),
|
|
167
169
|
Refusal::TypeMismatch(format!(
|
|
168
170
|
"named/declared query {question:?} is not generated for this domain — either unknown, or a \
|
|
169
|
-
real declared query whose shape this generator's codegen doesn't cover yet (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
real declared query whose shape this generator's codegen doesn't cover yet (cursor/\
|
|
172
|
+
consistency/freshness/inspection/index_hints, a where clause on a field the aggregate \
|
|
173
|
+
does not declare, or a literal comparator value whose true JSON type can't be \
|
|
174
|
+
recovered from the exported IR — hecks-codegen's manifest names which); a declared \
|
|
175
|
+
query with or without a where, ordered and bounded as declared, and the ad hoc \
|
|
174
176
|
filter shape ({{\"aggregate\",\"field\",\"op\",\"value\"}}) both execute for real"
|
|
175
177
|
)),
|
|
176
178
|
)),
|
data/rust/src/kernel/dispatch.rs
CHANGED
|
@@ -118,7 +118,7 @@ pub fn dispatch<'a, T, R>(
|
|
|
118
118
|
payload: Json,
|
|
119
119
|
mutations: &mut Vec<MutationRecord>,
|
|
120
120
|
// The synchronous half of `projects`, applied right before `repo.save`.
|
|
121
|
-
seed_projections: Vec<(&'static str, Option<
|
|
121
|
+
seed_projections: Vec<(&'static str, Option<Value>)>,
|
|
122
122
|
// The write-side tenant boundary. The router computes it (it needs every aggregate's repo)
|
|
123
123
|
// but it is applied here, first in `Save`, before `seed_projections`.
|
|
124
124
|
tenant_boundary_check: Result<(), Refusal>,
|
|
@@ -606,7 +606,7 @@ pub fn dispatch_entity<'a, T, E, R>(
|
|
|
606
606
|
payload: Json,
|
|
607
607
|
mutations: &mut Vec<MutationRecord>,
|
|
608
608
|
// As in `dispatch`: an entity command still ends in a parent aggregate save.
|
|
609
|
-
seed_projections: Vec<(&'static str, Option<
|
|
609
|
+
seed_projections: Vec<(&'static str, Option<Value>)>,
|
|
610
610
|
) -> Result<(T, Vec<Event>), Refusal>
|
|
611
611
|
where
|
|
612
612
|
T: Fielded + Clone + ToJson + SetProjectedField,
|
|
@@ -60,7 +60,7 @@ impl ToJson for Acct {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
impl SetProjectedField for Acct {
|
|
63
|
-
fn set_projected_field(&mut self, _name: &'static str, _value: Option<
|
|
63
|
+
fn set_projected_field(&mut self, _name: &'static str, _value: Option<Value>) {}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
fn rule(description: &'static str, field: &'static str) -> InvariantSpec {
|
data/rust/src/kernel/expr.rs
CHANGED
|
@@ -27,6 +27,39 @@ impl Value {
|
|
|
27
27
|
pub fn truthy(&self) -> bool {
|
|
28
28
|
!matches!(self, Value::Nil | Value::Bool(false))
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
/// The text of a `Str`, else `None`; a generated `SetProjectedField` arm narrows a
|
|
32
|
+
/// projected scalar to its field's type with one of the `into_*` readers.
|
|
33
|
+
pub fn into_string(self) -> Option<String> {
|
|
34
|
+
match self {
|
|
35
|
+
Value::Str(s) => Some(s),
|
|
36
|
+
_ => None,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// The integer of an `Int`, else `None`.
|
|
41
|
+
pub fn into_i64(self) -> Option<i64> {
|
|
42
|
+
match self {
|
|
43
|
+
Value::Int(n) => Some(n),
|
|
44
|
+
_ => None,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// The float of a `Float`, else `None`.
|
|
49
|
+
pub fn into_f64(self) -> Option<f64> {
|
|
50
|
+
match self {
|
|
51
|
+
Value::Float(f) => Some(f),
|
|
52
|
+
_ => None,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// The boolean of a `Bool`, else `None`.
|
|
57
|
+
pub fn into_bool(self) -> Option<bool> {
|
|
58
|
+
match self {
|
|
59
|
+
Value::Bool(b) => Some(b),
|
|
60
|
+
_ => None,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
30
63
|
}
|
|
31
64
|
|
|
32
65
|
pub enum Field<'a> {
|
|
@@ -203,6 +236,10 @@ pub enum Expr {
|
|
|
203
236
|
Bool(bool),
|
|
204
237
|
Nil,
|
|
205
238
|
Add(Box<Expr>, Box<Expr>),
|
|
239
|
+
Sub(Box<Expr>, Box<Expr>),
|
|
240
|
+
Mul(Box<Expr>, Box<Expr>),
|
|
241
|
+
/// Floor division on integers; a zero divisor is an evaluation fault.
|
|
242
|
+
Div(Box<Expr>, Box<Expr>),
|
|
206
243
|
SignTest { op: Comparison, receiver: Box<Expr> },
|
|
207
244
|
Empty(Box<Expr>),
|
|
208
245
|
ToS(Box<Expr>),
|
|
@@ -232,6 +269,9 @@ pub enum Expr {
|
|
|
232
269
|
/// `receiver.split("SEP")` — `Resolver::Split`. Produces a real
|
|
233
270
|
/// `Value::Array`, not `Value::List` — see that variant's own header.
|
|
234
271
|
Split { receiver: Box<Expr>, separator: String },
|
|
272
|
+
/// `receiver.strip` / `.lstrip` / `.rstrip` — `Resolver::Strip`. Trims Ruby's own whitespace
|
|
273
|
+
/// set (see `StripSide::trim`), not Unicode spaces.
|
|
274
|
+
Strip { receiver: Box<Expr>, side: StripSide },
|
|
235
275
|
/// `receiver.start_with?("prefix")` — `Resolver::StartsWith`.
|
|
236
276
|
StartsWith { receiver: Box<Expr>, substring: String },
|
|
237
277
|
/// `receiver.end_with?("suffix")` — `Resolver::EndsWith`.
|
|
@@ -242,6 +282,29 @@ pub enum Expr {
|
|
|
242
282
|
Last(Box<Expr>),
|
|
243
283
|
}
|
|
244
284
|
|
|
285
|
+
/// Which ends of a string `Expr::Strip` trims: `.strip` both, `.lstrip` the start, `.rstrip` the end.
|
|
286
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
287
|
+
pub enum StripSide {
|
|
288
|
+
Both,
|
|
289
|
+
Left,
|
|
290
|
+
Right,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
impl StripSide {
|
|
294
|
+
/// Ruby's `String#strip` family: trims null, tab, line feed, vertical tab, form feed, carriage
|
|
295
|
+
/// return and space, never a Unicode space. Rust's own `trim` would also drop U+00A0 and the
|
|
296
|
+
/// rest, so the set is spelled out.
|
|
297
|
+
pub fn trim(self, text: &str) -> String {
|
|
298
|
+
let is_stripped = |c: char| matches!(c, '\0' | '\t' | '\n' | '\u{b}' | '\u{c}' | '\r' | ' ');
|
|
299
|
+
match self {
|
|
300
|
+
StripSide::Both => text.trim_matches(is_stripped),
|
|
301
|
+
StripSide::Left => text.trim_start_matches(is_stripped),
|
|
302
|
+
StripSide::Right => text.trim_end_matches(is_stripped),
|
|
303
|
+
}
|
|
304
|
+
.to_string()
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
245
308
|
/// `args`/`instance` from `Evaluator.call` — `args` is checked first for
|
|
246
309
|
/// every `Lookup`, matching `Resolver#fetch`. Pass `&NoFields` for `{}`.
|
|
247
310
|
pub struct EvalContext<'a> {
|
|
@@ -274,14 +337,14 @@ fn category_of(expr: &Expr) -> OperatorCategory {
|
|
|
274
337
|
Or(..) | And(..) | Not(..) => OperatorCategory::Logical,
|
|
275
338
|
Include { .. } => OperatorCategory::Membership,
|
|
276
339
|
Compare { .. } => OperatorCategory::Comparison,
|
|
277
|
-
Add(..) | Modulo { .. } => OperatorCategory::Arithmetic,
|
|
340
|
+
Add(..) | Sub(..) | Mul(..) | Div(..) | Modulo { .. } => OperatorCategory::Arithmetic,
|
|
278
341
|
SignTest { .. } => OperatorCategory::SignTest,
|
|
279
342
|
Empty(..) | Size(..) => OperatorCategory::Sized,
|
|
280
343
|
ToS(..) => OperatorCategory::ToString,
|
|
281
344
|
BlockPredicate { .. } | Find { .. } => OperatorCategory::Enumeration,
|
|
282
345
|
MatchesRegex { .. } => OperatorCategory::PatternMatch,
|
|
283
346
|
Presence { .. } | Assignment { .. } => OperatorCategory::Presence,
|
|
284
|
-
Split { .. } | StartsWith { .. } | EndsWith { .. } => OperatorCategory::Text,
|
|
347
|
+
Split { .. } | Strip { .. } | StartsWith { .. } | EndsWith { .. } => OperatorCategory::Text,
|
|
285
348
|
First(..) | Last(..) => OperatorCategory::Positional,
|
|
286
349
|
Int(..) | Float(..) | Str(..) | Bool(..) | Nil | Lookup(..) | Array(..) => {
|
|
287
350
|
unreachable!("interpret's own leaf arms handle these before category_of is ever called")
|
|
@@ -300,7 +363,7 @@ fn dispatch_operator(category: OperatorCategory, expr: &Expr, ctx: &EvalContext)
|
|
|
300
363
|
OperatorCategory::Membership => membership::interpret(expr, ctx),
|
|
301
364
|
OperatorCategory::Comparison => comparison::interpret(expr, ctx),
|
|
302
365
|
OperatorCategory::Arithmetic => match expr {
|
|
303
|
-
Expr::Add(..) => arithmetic::
|
|
366
|
+
Expr::Add(..) | Expr::Sub(..) | Expr::Mul(..) | Expr::Div(..) => arithmetic::binary(expr, ctx),
|
|
304
367
|
Expr::Modulo { .. } => arithmetic::modulo(expr, ctx),
|
|
305
368
|
_ => Err(Refusal::TypeMismatch(format!("dispatch_operator(Arithmetic, ..) called with {expr:?} — a router bug"))),
|
|
306
369
|
},
|
|
@@ -1,15 +1,64 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//! `+`, `-`, `*`, `/` and `.modulo` (`Resolver::Addition`/`Subtraction`/`Multiplication`/`Division`/
|
|
2
|
+
//! `Modulo`); all require numeric operands.
|
|
2
3
|
|
|
3
4
|
use crate::kernel::attribute_shapes::scalar;
|
|
4
5
|
use crate::kernel::expr::{eval_error, interpret as eval, EvalContext, Expr, Value};
|
|
5
6
|
use crate::kernel::Refusal;
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/// `-`, `*`, `/` and `+`, each the counterpart of the Ruby `Resolver` operation of that name.
|
|
9
|
+
pub fn binary(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
|
|
10
|
+
let (name, symbol, l, r) = match expr {
|
|
11
|
+
Expr::Add(l, r) => ("addition", "+", l, r),
|
|
12
|
+
Expr::Sub(l, r) => ("subtraction", "-", l, r),
|
|
13
|
+
Expr::Mul(l, r) => ("multiplication", "*", l, r),
|
|
14
|
+
Expr::Div(l, r) => ("division", "/", l, r),
|
|
15
|
+
_ => return Err(Refusal::TypeMismatch(format!("arithmetic::binary called with a non-binary node {expr:?} — a router bug"))),
|
|
10
16
|
};
|
|
17
|
+
let (left, right) = (eval(l, ctx)?, eval(r, ctx)?);
|
|
18
|
+
match symbol {
|
|
19
|
+
"+" => sum(&left, &right),
|
|
20
|
+
"-" => checked(name, symbol, &left, &right, i64::checked_sub, |a, b| a - b),
|
|
21
|
+
"*" => checked(name, symbol, &left, &right, i64::checked_mul, |a, b| a * b),
|
|
22
|
+
_ => divide(&left, &right),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Integer operands use the checked operation; anything else is a Float result that must stay
|
|
27
|
+
// finite, as `add` does. Ruby promotes to Bignum; this kernel refuses instead.
|
|
28
|
+
fn checked(name: &str, symbol: &str, lhs: &Value, rhs: &Value, int_op: fn(i64, i64) -> Option<i64>, float_op: fn(f64, f64) -> f64) -> Result<Value, Refusal> {
|
|
29
|
+
if let (Value::Int(l), Value::Int(r)) = (lhs, rhs) {
|
|
30
|
+
return int_op(*l, *r).map(Value::Int).ok_or_else(|| eval_error(format!("{name} overflowed: {l} {symbol} {r} does not fit in a 64-bit integer")));
|
|
31
|
+
}
|
|
32
|
+
let l = require_number(lhs, name)?;
|
|
33
|
+
let r = require_number(rhs, name)?;
|
|
34
|
+
finite(name, symbol, l, r, float_op(l, r))
|
|
35
|
+
}
|
|
11
36
|
|
|
12
|
-
|
|
37
|
+
fn finite(name: &str, symbol: &str, l: f64, r: f64, result: f64) -> Result<Value, Refusal> {
|
|
38
|
+
if result.is_finite() {
|
|
39
|
+
return Ok(Value::Float(result));
|
|
40
|
+
}
|
|
41
|
+
Err(eval_error(format!("{name} overflowed: {l} {symbol} {r} is not a finite number")))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Integer division floors toward negative infinity, as Ruby's `Integer#/` does (Rust's `/`
|
|
45
|
+
// truncates). A zero divisor is the same fault `.modulo` raises.
|
|
46
|
+
fn divide(lhs: &Value, rhs: &Value) -> Result<Value, Refusal> {
|
|
47
|
+
let r = require_number(rhs, "division")?;
|
|
48
|
+
if r == 0.0 {
|
|
49
|
+
return Err(eval_error("divided by 0".to_string()));
|
|
50
|
+
}
|
|
51
|
+
if let (Value::Int(l), Value::Int(r)) = (lhs, rhs) {
|
|
52
|
+
return floored_div(*l, *r).map(Value::Int).ok_or_else(|| eval_error(format!("division overflowed: {l} / {r} does not fit in a 64-bit integer")));
|
|
53
|
+
}
|
|
54
|
+
let l = require_number(lhs, "division")?;
|
|
55
|
+
finite("division", "/", l, r, l / r)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// `None` only for `i64::MIN / -1`, the one quotient that does not fit.
|
|
59
|
+
fn floored_div(l: i64, r: i64) -> Option<i64> {
|
|
60
|
+
let quotient = l.checked_div(r)?;
|
|
61
|
+
Some(if l % r != 0 && ((l < 0) != (r < 0)) { quotient - 1 } else { quotient })
|
|
13
62
|
}
|
|
14
63
|
|
|
15
64
|
pub fn modulo(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
|
|
@@ -35,6 +84,47 @@ fn floored_mod(r: i64, d: i64) -> i64 {
|
|
|
35
84
|
if raw != 0 && (raw < 0) != (d < 0) { raw + d } else { raw }
|
|
36
85
|
}
|
|
37
86
|
|
|
87
|
+
#[cfg(test)]
|
|
88
|
+
mod division_tests {
|
|
89
|
+
use super::{checked, divide, floored_div};
|
|
90
|
+
use crate::kernel::expr::Value;
|
|
91
|
+
|
|
92
|
+
#[test]
|
|
93
|
+
fn floors_toward_negative_infinity_across_every_sign_combination() {
|
|
94
|
+
// Real `ruby -e 'puts X / Y'` results.
|
|
95
|
+
assert_eq!(floored_div(7, 2), Some(3));
|
|
96
|
+
assert_eq!(floored_div(-7, 2), Some(-4));
|
|
97
|
+
assert_eq!(floored_div(7, -2), Some(-4));
|
|
98
|
+
assert_eq!(floored_div(-7, -2), Some(3));
|
|
99
|
+
assert_eq!(floored_div(-6, 2), Some(-3));
|
|
100
|
+
assert_eq!(floored_div(0, -5), Some(0));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[test]
|
|
104
|
+
fn a_money_percentage_rounds_down() {
|
|
105
|
+
let product = checked("multiplication", "*", &Value::Int(10801), &Value::Int(50), i64::checked_mul, |a, b| a * b).unwrap();
|
|
106
|
+
|
|
107
|
+
assert_eq!(divide(&product, &Value::Int(100)).unwrap(), Value::Int(5400));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn a_zero_divisor_is_a_fault_not_a_crash() {
|
|
112
|
+
assert!(divide(&Value::Int(1), &Value::Int(0)).is_err());
|
|
113
|
+
assert!(divide(&Value::Float(1.0), &Value::Float(0.0)).is_err());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#[test]
|
|
117
|
+
fn the_one_unrepresentable_quotient_is_a_fault() {
|
|
118
|
+
assert!(divide(&Value::Int(i64::MIN), &Value::Int(-1)).is_err());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[test]
|
|
122
|
+
fn overflowing_products_and_differences_are_faults() {
|
|
123
|
+
assert!(checked("multiplication", "*", &Value::Int(i64::MAX), &Value::Int(2), i64::checked_mul, |a, b| a * b).is_err());
|
|
124
|
+
assert!(checked("subtraction", "-", &Value::Int(i64::MIN), &Value::Int(1), i64::checked_sub, |a, b| a - b).is_err());
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
38
128
|
#[cfg(test)]
|
|
39
129
|
mod tests {
|
|
40
130
|
use super::floored_mod;
|
|
@@ -126,3 +216,38 @@ fn sum(lhs: &Value, rhs: &Value) -> Result<Value, Refusal> {
|
|
|
126
216
|
pub fn require_number(v: &Value, operation: &str) -> Result<f64, Refusal> {
|
|
127
217
|
scalar::numeric(v).ok_or_else(|| eval_error(format!("{operation} expects a number, got {v:?}")))
|
|
128
218
|
}
|
|
219
|
+
|
|
220
|
+
#[cfg(test)]
|
|
221
|
+
mod path_operand_tests {
|
|
222
|
+
use crate::kernel::expr::{interpret, EvalContext, Expr, Field, Fielded, NoFields, Value};
|
|
223
|
+
|
|
224
|
+
// Shaped like a generated multi-field value object: `Charge { cents, currency }`.
|
|
225
|
+
struct Charge(i64);
|
|
226
|
+
impl Fielded for Charge {
|
|
227
|
+
fn field(&self, name: &str) -> Option<Field<'_>> {
|
|
228
|
+
match name {
|
|
229
|
+
"cents" => Some(Field::Value(Value::Int(self.0))),
|
|
230
|
+
"currency" => Some(Field::Value(Value::Str("USD".to_string()))),
|
|
231
|
+
_ => None,
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
struct Record(Charge);
|
|
237
|
+
impl Fielded for Record {
|
|
238
|
+
fn field(&self, name: &str) -> Option<Field<'_>> {
|
|
239
|
+
(name == "charged").then(|| Field::Nested(&self.0))
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
fn percent_of(path: &'static str) -> Value {
|
|
244
|
+
let times = Expr::Mul(Box::new(Expr::Lookup(path)), Box::new(Expr::Int(50)));
|
|
245
|
+
let expr = Expr::Div(Box::new(times), Box::new(Expr::Int(100)));
|
|
246
|
+
interpret(&expr, &EvalContext { args: &NoFields, instance: &Record(Charge(10801)) }).expect("evaluates")
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#[test]
|
|
250
|
+
fn an_operand_reads_a_field_inside_a_value_object_by_path() {
|
|
251
|
+
assert_eq!(percent_of("charged.cents"), Value::Int(5400));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//! The "text" expression-operator category: `.split`, `.
|
|
2
|
-
//! String receivers only; `expr.rs` routes only these
|
|
1
|
+
//! The "text" expression-operator category: `.split`, `.strip` (and `.lstrip`/`.rstrip`),
|
|
2
|
+
//! `.start_with?`, `.end_with?`. String receivers only; `expr.rs` routes only these nodes here.
|
|
3
3
|
|
|
4
4
|
use crate::kernel::expr::{eval_error, interpret as eval, EvalContext, Expr, Value};
|
|
5
5
|
use crate::kernel::Refusal;
|
|
@@ -15,6 +15,10 @@ pub fn interpret(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
|
|
|
15
15
|
}
|
|
16
16
|
Ok(Value::Array(parts.into_iter().map(|p| Value::Str(p.to_string())).collect()))
|
|
17
17
|
}
|
|
18
|
+
Expr::Strip { receiver, side } => {
|
|
19
|
+
let text = require_str(&eval(receiver, ctx)?, "strip")?;
|
|
20
|
+
Ok(Value::Str(side.trim(&text)))
|
|
21
|
+
}
|
|
18
22
|
Expr::StartsWith { receiver, substring } => {
|
|
19
23
|
let text = require_str(&eval(receiver, ctx)?, "start_with?")?;
|
|
20
24
|
Ok(Value::Bool(text.starts_with(substring.as_str())))
|
|
@@ -63,6 +67,26 @@ mod tests {
|
|
|
63
67
|
assert_eq!(run(phrase), Value::Array(vec![Value::Str("abc".to_string())]));
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
#[test]
|
|
71
|
+
fn strip_trims_rubys_whitespace_set_and_nothing_else() {
|
|
72
|
+
use crate::kernel::expr::StripSide;
|
|
73
|
+
let strip = |side, text: &str| run(Expr::Strip { receiver: Box::new(str(text)), side });
|
|
74
|
+
assert_eq!(strip(StripSide::Both, " \t\n a b \r\u{b}\u{c}\0"), Value::Str("a b".to_string()));
|
|
75
|
+
assert_eq!(strip(StripSide::Left, "\0 a "), Value::Str("a ".to_string()));
|
|
76
|
+
assert_eq!(strip(StripSide::Right, " a \0 "), Value::Str(" a".to_string()));
|
|
77
|
+
assert_eq!(strip(StripSide::Both, " "), Value::Str(String::new()));
|
|
78
|
+
// A non-breaking space is not Ruby whitespace, so it stays.
|
|
79
|
+
assert_eq!(strip(StripSide::Both, "\u{a0}a\u{a0}"), Value::Str("\u{a0}a\u{a0}".to_string()));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[test]
|
|
83
|
+
fn strip_on_a_non_string_refuses_with_the_operator_named() {
|
|
84
|
+
use crate::kernel::expr::StripSide;
|
|
85
|
+
let ctx = EvalContext { args: &NoFields, instance: &NoFields };
|
|
86
|
+
let err = eval(&Expr::Strip { receiver: Box::new(Expr::Int(1)), side: StripSide::Both }, &ctx).unwrap_err();
|
|
87
|
+
assert!(format!("{err:?}").contains("strip expects a string"), "{err:?}");
|
|
88
|
+
}
|
|
89
|
+
|
|
66
90
|
#[test]
|
|
67
91
|
fn start_and_end_with() {
|
|
68
92
|
assert_eq!(run(Expr::StartsWith { receiver: Box::new(str("{}")), substring: "{".to_string() }), Value::Bool(true));
|