hecks 3.13.0 → 4.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/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +1 -7
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/named_pattern.rb +33 -0
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +3 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/domain_stub.rb +1 -1
- data/lib/hecks/cli/interview_aggregate.rb +4 -4
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/framework/bluebook/change.bluebook +2 -2
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +58 -37
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +15 -92
- data/lib/hecks/hecks/hecks.bluebook +2 -2
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +10 -16
- data/lib/hecks/hecks/tooling.bluebook +22 -22
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
- data/lib/hecks/language/bluebook/command.bluebook +1 -0
- data/lib/hecks/language/bluebook/entity.bluebook +1 -0
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +8 -7
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +51 -51
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value.rb +13 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/site/bluebook/site.bluebook +5 -5
- data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
- data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +300 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +145 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +606 -205
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +990 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +191 -0
- data/rust/host/src/payments/tests.rs +1445 -275
- data/rust/host/src/payments.rs +409 -121
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +483 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -0
- data/rust/parser/src/build/named_pattern.rs +26 -0
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -510
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +43 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +229 -112
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +230 -148
- data/rust/src/kernel/mod.rs +46 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
- data/rust/host/src/approval.rs +0 -652
data/rust/parser/src/lex.rs
CHANGED
|
@@ -93,7 +93,6 @@ fn ends_outside_quotes(text: &str) -> bool {
|
|
|
93
93
|
}
|
|
94
94
|
if ch == '"' {
|
|
95
95
|
quoting = !quoting;
|
|
96
|
-
continue;
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
!quoting
|
|
@@ -163,12 +162,8 @@ fn strip_comment(line: &str) -> &str {
|
|
|
163
162
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
164
163
|
pub enum Opener {
|
|
165
164
|
None,
|
|
166
|
-
DoBlock {
|
|
167
|
-
|
|
168
|
-
},
|
|
169
|
-
BraceBlock {
|
|
170
|
-
body: String,
|
|
171
|
-
},
|
|
165
|
+
DoBlock { params: Option<String> },
|
|
166
|
+
BraceBlock { body: String },
|
|
172
167
|
}
|
|
173
168
|
|
|
174
169
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
@@ -194,7 +189,7 @@ const FORBIDDEN_LEADING_WORDS: &[&str] = &[
|
|
|
194
189
|
/// Only a file's top line is receiver-qualified; a nested `Hecks.x` must stay an error.
|
|
195
190
|
const FILE_RECEIVER_PREFIX: &str = "Hecks.";
|
|
196
191
|
|
|
197
|
-
pub fn classify
|
|
192
|
+
pub fn classify(file: &str, line: &SourceLine<'_>) -> Result<LineShape, Diagnostic> {
|
|
198
193
|
let text = line
|
|
199
194
|
.text
|
|
200
195
|
.strip_prefix(FILE_RECEIVER_PREFIX)
|
|
@@ -232,9 +227,8 @@ pub fn classify<'a>(file: &str, line: &SourceLine<'a>) -> Result<LineShape, Diag
|
|
|
232
227
|
file,
|
|
233
228
|
line.number,
|
|
234
229
|
format!(
|
|
235
|
-
"'{}' looks like a local variable assignment at byte {top_level_eq} — this \
|
|
236
|
-
parser never interprets bare Ruby expressions or assignment"
|
|
237
|
-
text
|
|
230
|
+
"'{text}' looks like a local variable assignment at byte {top_level_eq} — this \
|
|
231
|
+
parser never interprets bare Ruby expressions or assignment"
|
|
238
232
|
),
|
|
239
233
|
));
|
|
240
234
|
}
|
|
@@ -256,9 +250,8 @@ fn leading_identifier_end(text: &str) -> usize {
|
|
|
256
250
|
if ch.is_ascii_alphabetic() || ch == '_' {
|
|
257
251
|
end = idx + ch.len_utf8();
|
|
258
252
|
continue;
|
|
259
|
-
} else {
|
|
260
|
-
return 0;
|
|
261
253
|
}
|
|
254
|
+
return 0;
|
|
262
255
|
}
|
|
263
256
|
if ch.is_ascii_alphanumeric() || ch == '_' {
|
|
264
257
|
end = idx + ch.len_utf8();
|
|
@@ -346,8 +339,7 @@ fn trailing_do(rest: &str) -> Option<(&str, Option<String>)> {
|
|
|
346
339
|
if let Some(before) = trimmed.strip_suffix(" do") {
|
|
347
340
|
return Some((before, None));
|
|
348
341
|
}
|
|
349
|
-
if trimmed.
|
|
350
|
-
let without_trailing_pipe = &trimmed[..trimmed.len() - 1];
|
|
342
|
+
if let Some(without_trailing_pipe) = trimmed.strip_suffix('|') {
|
|
351
343
|
if let Some(open_pipe) = without_trailing_pipe.rfind('|') {
|
|
352
344
|
let params = &without_trailing_pipe[open_pipe + 1..];
|
|
353
345
|
let before_pipe = trimmed[..open_pipe].trim_end();
|
|
@@ -400,10 +392,7 @@ fn find_top_level_brace(text: &str) -> Option<usize> {
|
|
|
400
392
|
|
|
401
393
|
/// A `{` after `:`, `,` or `>` opens a hash literal; `>` covers a parenless `:k => { ... }`.
|
|
402
394
|
fn is_hash_literal_brace(before: &str) -> bool {
|
|
403
|
-
matches!(
|
|
404
|
-
before.trim_end().chars().last(),
|
|
405
|
-
Some(':') | Some(',') | Some('>')
|
|
406
|
-
)
|
|
395
|
+
matches!(before.trim_end().chars().last(), Some(':' | ',' | '>'))
|
|
407
396
|
}
|
|
408
397
|
|
|
409
398
|
fn matching_brace_body(text: &str) -> Option<(String, &str)> {
|
|
@@ -488,7 +477,7 @@ pub fn capture_do_block_body<'a>(
|
|
|
488
477
|
|
|
489
478
|
loop {
|
|
490
479
|
let line = *lines.get(*pos).ok_or_else(|| {
|
|
491
|
-
let last = lines.last().
|
|
480
|
+
let last = lines.last().map_or(0, |l| l.number);
|
|
492
481
|
Diagnostic::new(
|
|
493
482
|
file,
|
|
494
483
|
last,
|
|
@@ -566,10 +555,7 @@ mod tests {
|
|
|
566
555
|
|
|
567
556
|
#[test]
|
|
568
557
|
fn does_not_strip_a_bare_lowercase_call() {
|
|
569
|
-
assert_eq!(
|
|
570
|
-
strip_aggregate_receiver("attaches \"Governance\""),
|
|
571
|
-
None
|
|
572
|
-
);
|
|
558
|
+
assert_eq!(strip_aggregate_receiver("attaches \"Governance\""), None);
|
|
573
559
|
assert_eq!(strip_aggregate_receiver("subscribe \"Deposited\""), None);
|
|
574
560
|
}
|
|
575
561
|
|
|
@@ -615,7 +601,7 @@ mod tests {
|
|
|
615
601
|
shape,
|
|
616
602
|
LineShape::Call(Call {
|
|
617
603
|
word: "identified_by".to_string(),
|
|
618
|
-
args:
|
|
604
|
+
args: String::new(),
|
|
619
605
|
opener: Opener::BraceBlock {
|
|
620
606
|
body: "name.value".to_string()
|
|
621
607
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
data/rust/parser/src/main.rs
CHANGED
|
@@ -6,7 +6,14 @@ mod canonical;
|
|
|
6
6
|
mod diag;
|
|
7
7
|
mod emit;
|
|
8
8
|
mod expr;
|
|
9
|
+
// The one `Hecks::Naming` port, compiled from `hecks-codegen`'s source: neither crate depends on
|
|
10
|
+
// the other. This crate reads only part of it.
|
|
11
|
+
#[allow(dead_code)]
|
|
12
|
+
#[path = "../../codegen/src/hecks_naming.rs"]
|
|
13
|
+
mod hecks_naming;
|
|
9
14
|
mod ir;
|
|
15
|
+
// Generated table; not every column is consumed by this binary.
|
|
16
|
+
#[allow(dead_code)]
|
|
10
17
|
mod keywords;
|
|
11
18
|
mod lex;
|
|
12
19
|
mod parse;
|
|
@@ -37,7 +44,7 @@ enum RunError {
|
|
|
37
44
|
|
|
38
45
|
impl From<diag::Diagnostic> for RunError {
|
|
39
46
|
fn from(d: diag::Diagnostic) -> Self {
|
|
40
|
-
|
|
47
|
+
Self::Parse(d)
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
|
|
@@ -45,12 +52,17 @@ fn run(args: &[String]) -> Result<(), RunError> {
|
|
|
45
52
|
match args.first().map(String::as_str) {
|
|
46
53
|
Some("chapter") => run_chapter(&args[1..]),
|
|
47
54
|
Some("resolve") => run_resolve(&args[1..]),
|
|
48
|
-
Some("
|
|
55
|
+
Some("adapter") => run_adapter(&args[1..]),
|
|
56
|
+
Some("world") => run_world(&args[1..]),
|
|
57
|
+
Some("coverage") => {
|
|
58
|
+
run_coverage();
|
|
59
|
+
Ok(())
|
|
60
|
+
}
|
|
49
61
|
Some(other) => Err(RunError::Usage(format!(
|
|
50
|
-
"unknown subcommand '{other}' — expected 'chapter', 'resolve', or 'coverage'"
|
|
62
|
+
"unknown subcommand '{other}' — expected 'chapter', 'resolve', 'adapter', 'world', or 'coverage'"
|
|
51
63
|
))),
|
|
52
64
|
None => Err(RunError::Usage(
|
|
53
|
-
"hecks-parse <chapter|resolve|coverage> ...".to_string(),
|
|
65
|
+
"hecks-parse <chapter|resolve|adapter|world|coverage> ...".to_string(),
|
|
54
66
|
)),
|
|
55
67
|
}
|
|
56
68
|
}
|
|
@@ -143,11 +155,59 @@ fn run_resolve(args: &[String]) -> Result<(), RunError> {
|
|
|
143
155
|
Ok(())
|
|
144
156
|
}
|
|
145
157
|
|
|
158
|
+
/// `hecks-parse adapter <file.adapter>...`: prints the declared adapters as a JSON array, in
|
|
159
|
+
/// file order then declaration order.
|
|
160
|
+
fn run_adapter(args: &[String]) -> Result<(), RunError> {
|
|
161
|
+
if args.is_empty() {
|
|
162
|
+
return Err(RunError::Usage(
|
|
163
|
+
"hecks-parse adapter <file.adapter>...".to_string(),
|
|
164
|
+
));
|
|
165
|
+
}
|
|
166
|
+
let mut adapters = Vec::new();
|
|
167
|
+
for path in args {
|
|
168
|
+
let source = fs::read_to_string(path)
|
|
169
|
+
.map_err(|e| RunError::Usage(format!("could not read '{path}': {e}")))?;
|
|
170
|
+
adapters.extend(parse::adapter::parse_file(path, &source)?);
|
|
171
|
+
}
|
|
172
|
+
let value = emit::JsonValue::Array(adapters.iter().map(emit::adapter_json).collect());
|
|
173
|
+
println!("{}", emit::write(&value));
|
|
174
|
+
Ok(())
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// `hecks-parse world <file.world>...`: prints one world per distinct domain, in order of first
|
|
178
|
+
/// appearance across the files, later blocks of a domain merged into the first.
|
|
179
|
+
fn run_world(args: &[String]) -> Result<(), RunError> {
|
|
180
|
+
if args.is_empty() {
|
|
181
|
+
return Err(RunError::Usage(
|
|
182
|
+
"hecks-parse world <file.world>...".to_string(),
|
|
183
|
+
));
|
|
184
|
+
}
|
|
185
|
+
let mut blocks = Vec::new();
|
|
186
|
+
for path in args {
|
|
187
|
+
let source = fs::read_to_string(path)
|
|
188
|
+
.map_err(|e| RunError::Usage(format!("could not read '{path}': {e}")))?;
|
|
189
|
+
blocks.extend(parse::world::parse_file(path, &source)?);
|
|
190
|
+
}
|
|
191
|
+
let worlds = parse::world::merge(blocks);
|
|
192
|
+
let value = emit::JsonValue::Array(worlds.iter().map(emit::world_json).collect());
|
|
193
|
+
println!("{}", emit::write(&value));
|
|
194
|
+
Ok(())
|
|
195
|
+
}
|
|
196
|
+
|
|
146
197
|
/// `(word, context)` pairs this parser builds real IR for, printed by `hecks-parse coverage`.
|
|
147
198
|
/// A word that is only gated, never built into IR, must stay off this list.
|
|
148
199
|
const COVERED_PAIRS: &[(&str, &str)] = &[
|
|
149
200
|
("bluebook", "File"),
|
|
150
201
|
("hecksagon", "File"),
|
|
202
|
+
("adapter", "File"),
|
|
203
|
+
("port", "Adapter"),
|
|
204
|
+
("field", "Adapter"),
|
|
205
|
+
("secret", "Adapter"),
|
|
206
|
+
("world", "File"),
|
|
207
|
+
("realm", "World"),
|
|
208
|
+
("latest", "World"),
|
|
209
|
+
("default_database", "World"),
|
|
210
|
+
("default_adapter", "World"),
|
|
151
211
|
("vision", "Bluebook"),
|
|
152
212
|
("core", "Bluebook"),
|
|
153
213
|
("supporting", "Bluebook"),
|
|
@@ -248,11 +308,10 @@ const COVERED_PAIRS: &[(&str, &str)] = &[
|
|
|
248
308
|
("one_of", "Type"),
|
|
249
309
|
];
|
|
250
310
|
|
|
251
|
-
fn run_coverage(
|
|
311
|
+
fn run_coverage() {
|
|
252
312
|
let pairs: Vec<String> = COVERED_PAIRS
|
|
253
313
|
.iter()
|
|
254
314
|
.map(|(word, context)| format!("[\"{word}\", \"{context}\"]"))
|
|
255
315
|
.collect();
|
|
256
316
|
println!("[{}]", pairs.join(", "));
|
|
257
|
-
Ok(())
|
|
258
317
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
//! The `Adapter` construct: a `.adapter` file's `Hecks.adapter "Name" do ... end` blocks, each
|
|
2
|
+
//! declaring a `port`, `field`s (optionally defaulted) and `secret`s. Printed by
|
|
3
|
+
//! `hecks-parse adapter`; adapters never enter `ir.json`.
|
|
4
|
+
|
|
5
|
+
use crate::diag::{Diagnostic, ParseResult};
|
|
6
|
+
use crate::ir;
|
|
7
|
+
use crate::lex::{self, SourceLine};
|
|
8
|
+
use crate::ruby_value::{self, Value};
|
|
9
|
+
|
|
10
|
+
/// Parses every top-level `adapter` block of one `.adapter` file, in file order.
|
|
11
|
+
///
|
|
12
|
+
/// A top-level line that is exactly `require "path"` is plain Ruby, not grammar, and is skipped;
|
|
13
|
+
/// any other top-level word goes through the normal gates and refuses there.
|
|
14
|
+
pub fn parse_file(path: &str, source: &str) -> ParseResult<Vec<ir::Adapter>> {
|
|
15
|
+
let joined = lex::join_continuations(source);
|
|
16
|
+
let lines = lex::lines(&joined);
|
|
17
|
+
let mut pos = 0usize;
|
|
18
|
+
let mut adapters = Vec::new();
|
|
19
|
+
|
|
20
|
+
while pos < lines.len() {
|
|
21
|
+
if is_require_line(lines[pos].text) {
|
|
22
|
+
pos += 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
let (row, call, header_line) = super::file::parse_header(path, &lines, &mut pos)?;
|
|
26
|
+
if row.inner != "Adapter" {
|
|
27
|
+
return Err(Diagnostic::new(
|
|
28
|
+
path,
|
|
29
|
+
header_line,
|
|
30
|
+
format!(
|
|
31
|
+
"a .adapter file's own top-level blocks must all be 'adapter'; got '{}'",
|
|
32
|
+
call.word
|
|
33
|
+
),
|
|
34
|
+
));
|
|
35
|
+
}
|
|
36
|
+
let name = super::file::header_name(&call)
|
|
37
|
+
.ok_or_else(|| Diagnostic::new(path, header_line, "'adapter' needs a quoted name"))?;
|
|
38
|
+
adapters.push(parse_body(path, &lines, &mut pos, name)?);
|
|
39
|
+
}
|
|
40
|
+
Ok(adapters)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// `require` followed by exactly one double-quoted string, nothing else.
|
|
44
|
+
fn is_require_line(text: &str) -> bool {
|
|
45
|
+
let Some(rest) = text.strip_prefix("require ") else {
|
|
46
|
+
return false;
|
|
47
|
+
};
|
|
48
|
+
let rest = rest.trim();
|
|
49
|
+
rest.len() >= 2
|
|
50
|
+
&& rest.starts_with('"')
|
|
51
|
+
&& rest.ends_with('"')
|
|
52
|
+
&& !rest[1..rest.len() - 1].contains(['"', '\\'])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn parse_body(
|
|
56
|
+
file: &str,
|
|
57
|
+
lines: &[SourceLine],
|
|
58
|
+
pos: &mut usize,
|
|
59
|
+
name: String,
|
|
60
|
+
) -> ParseResult<ir::Adapter> {
|
|
61
|
+
let mut adapter = ir::Adapter {
|
|
62
|
+
name,
|
|
63
|
+
..ir::Adapter::default()
|
|
64
|
+
};
|
|
65
|
+
while let Some(gated) = super::next_line(file, lines, pos, "Adapter")? {
|
|
66
|
+
let number = gated.line.number;
|
|
67
|
+
match gated.row.word {
|
|
68
|
+
// A repeat replaces the earlier value, as the Ruby builder's setter does.
|
|
69
|
+
"port" => {
|
|
70
|
+
adapter.port = Some(super::positional_text(
|
|
71
|
+
file,
|
|
72
|
+
number,
|
|
73
|
+
"port",
|
|
74
|
+
&gated.args,
|
|
75
|
+
1,
|
|
76
|
+
)?);
|
|
77
|
+
}
|
|
78
|
+
"field" => {
|
|
79
|
+
let field = super::positional_symbol(file, number, "field", &gated.args, 1)?;
|
|
80
|
+
if let Some(raw) = super::named_raw(&gated.args, "default") {
|
|
81
|
+
let value = default_value(file, number, raw)?;
|
|
82
|
+
// A repeated key keeps its first position with the last value.
|
|
83
|
+
match adapter.defaults.iter_mut().find(|(k, _)| *k == field) {
|
|
84
|
+
Some(slot) => slot.1 = value,
|
|
85
|
+
None => adapter.defaults.push((field.clone(), value)),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
adapter.fields.push(field);
|
|
89
|
+
}
|
|
90
|
+
"secret" => {
|
|
91
|
+
adapter.secrets.push(super::positional_symbol(
|
|
92
|
+
file,
|
|
93
|
+
number,
|
|
94
|
+
"secret",
|
|
95
|
+
&gated.args,
|
|
96
|
+
1,
|
|
97
|
+
)?);
|
|
98
|
+
}
|
|
99
|
+
_ => {
|
|
100
|
+
return Err(super::not_built_yet(
|
|
101
|
+
"Adapter",
|
|
102
|
+
gated.row,
|
|
103
|
+
file,
|
|
104
|
+
number,
|
|
105
|
+
&gated.call.word,
|
|
106
|
+
))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
Ok(adapter)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// A JSON-native default: string, integer, boolean or nil. Anything else is refused rather than
|
|
114
|
+
/// guessed at, and a string may use braces only for the `{name}` and `{storage_name}`
|
|
115
|
+
/// placeholders (Ruby's `^(?:[^{}]|\{(?:name|storage_name)\})*$`).
|
|
116
|
+
fn default_value(file: &str, line: usize, raw: &str) -> ParseResult<Value> {
|
|
117
|
+
let value = ruby_value::read(raw);
|
|
118
|
+
match &value {
|
|
119
|
+
Value::Nil | Value::Bool(_) | Value::Int(_) => {}
|
|
120
|
+
Value::Str(text) if braces_are_placeholders(text) => {}
|
|
121
|
+
Value::Str(text) => {
|
|
122
|
+
return Err(Diagnostic::new(
|
|
123
|
+
file,
|
|
124
|
+
line,
|
|
125
|
+
format!(
|
|
126
|
+
"'field' default \"{text}\" uses braces; only {{name}} and {{storage_name}} are allowed"
|
|
127
|
+
),
|
|
128
|
+
))
|
|
129
|
+
}
|
|
130
|
+
_ => {
|
|
131
|
+
return Err(Diagnostic::new(
|
|
132
|
+
file,
|
|
133
|
+
line,
|
|
134
|
+
format!(
|
|
135
|
+
"'field' default ({}) must be a string, integer, true, false or nil",
|
|
136
|
+
raw.trim()
|
|
137
|
+
),
|
|
138
|
+
))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
Ok(value)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fn braces_are_placeholders(text: &str) -> bool {
|
|
145
|
+
let mut rest = text;
|
|
146
|
+
while let Some(at) = rest.find(['{', '}']) {
|
|
147
|
+
let tail = &rest[at..];
|
|
148
|
+
let Some(after) = ["{name}", "{storage_name}"]
|
|
149
|
+
.iter()
|
|
150
|
+
.find_map(|p| tail.strip_prefix(p))
|
|
151
|
+
else {
|
|
152
|
+
return false;
|
|
153
|
+
};
|
|
154
|
+
rest = after;
|
|
155
|
+
}
|
|
156
|
+
true
|
|
157
|
+
}
|
|
@@ -107,6 +107,16 @@ pub struct PendingChapterGiven {
|
|
|
107
107
|
pub file: String,
|
|
108
108
|
pub line: usize,
|
|
109
109
|
}
|
|
110
|
+
/// An aggregate with its policies and the givens left for the chapter to resolve.
|
|
111
|
+
pub type ParsedAggregate = (
|
|
112
|
+
ir::Aggregate,
|
|
113
|
+
Vec<ir::Policy>,
|
|
114
|
+
Vec<PendingChapterGiven>,
|
|
115
|
+
Vec<(usize, command::PendingCommandGiven)>,
|
|
116
|
+
Vec<entity::PendingChapterEntityGiven>,
|
|
117
|
+
Vec<entity::PendingEntityCommandGiven>,
|
|
118
|
+
);
|
|
119
|
+
|
|
110
120
|
/// Type-form `identified_by` resolves after the loop: its value object may be declared later.
|
|
111
121
|
pub fn parse_body(
|
|
112
122
|
file: &str,
|
|
@@ -116,14 +126,7 @@ pub fn parse_body(
|
|
|
116
126
|
chapter_named_givens: &mut Vec<(String, ir::Given)>,
|
|
117
127
|
// Chapter-wide entity-scoped pool, passed unchanged to every top-level entity.
|
|
118
128
|
chapter_entity_named_givens: &mut Vec<(String, ir::Given)>,
|
|
119
|
-
) -> ParseResult<
|
|
120
|
-
ir::Aggregate,
|
|
121
|
-
Vec<ir::Policy>,
|
|
122
|
-
Vec<PendingChapterGiven>,
|
|
123
|
-
Vec<(usize, command::PendingCommandGiven)>,
|
|
124
|
-
Vec<entity::PendingChapterEntityGiven>,
|
|
125
|
-
Vec<entity::PendingEntityCommandGiven>,
|
|
126
|
-
)> {
|
|
129
|
+
) -> ParseResult<ParsedAggregate> {
|
|
127
130
|
let mut aggregate = ir::Aggregate {
|
|
128
131
|
name: name.to_string(),
|
|
129
132
|
..Default::default()
|
|
@@ -189,7 +192,7 @@ pub fn parse_body(
|
|
|
189
192
|
"description",
|
|
190
193
|
&gated.args,
|
|
191
194
|
1,
|
|
192
|
-
)?)
|
|
195
|
+
)?);
|
|
193
196
|
}
|
|
194
197
|
// Origin, not identity: captured raw, like a literal Hash default.
|
|
195
198
|
"provenance" => {
|
|
@@ -600,7 +603,13 @@ pub fn parse_body(
|
|
|
600
603
|
}
|
|
601
604
|
}
|
|
602
605
|
|
|
603
|
-
lifecycle::seal_commands(
|
|
606
|
+
lifecycle::seal_commands(
|
|
607
|
+
file,
|
|
608
|
+
*pos,
|
|
609
|
+
&aggregate.name,
|
|
610
|
+
aggregate.lifecycle.as_ref(),
|
|
611
|
+
&aggregate.commands,
|
|
612
|
+
)?;
|
|
604
613
|
|
|
605
614
|
Ok((
|
|
606
615
|
aggregate,
|
|
@@ -58,15 +58,11 @@ pub fn parse_chapter(chapter_name: &str, files: &[(String, String)]) -> ParseRes
|
|
|
58
58
|
match row.inner {
|
|
59
59
|
"Bluebook" => {
|
|
60
60
|
let declared_version = super::file::header_version(&call);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
built.version = declared_version.clone();
|
|
67
|
-
bluebook = Some(built);
|
|
68
|
-
}
|
|
69
|
-
let target = bluebook.as_mut().expect("just set above");
|
|
61
|
+
let target = bluebook.get_or_insert_with(|| ir::Bluebook {
|
|
62
|
+
name: chapter_name.to_string(),
|
|
63
|
+
version: declared_version.clone(),
|
|
64
|
+
..Default::default()
|
|
65
|
+
});
|
|
70
66
|
if let Some(version) = declared_version {
|
|
71
67
|
match target.version.as_deref() {
|
|
72
68
|
Some(existing) if existing != version => {
|
|
@@ -373,6 +369,7 @@ pub fn resolve_hecksagon_dependencies(
|
|
|
373
369
|
|
|
374
370
|
/// Parses a `Hecks.bluebook "Name" do ... end` body into an existing accumulator.
|
|
375
371
|
/// Policies bubble as in `BluebookBuilder#build`: aggregate policies first, then chapter-level.
|
|
372
|
+
#[allow(clippy::too_many_arguments)] // established signature
|
|
376
373
|
fn parse_body_into(
|
|
377
374
|
file: &str,
|
|
378
375
|
lines: &[SourceLine],
|
|
@@ -389,10 +386,7 @@ fn parse_body_into(
|
|
|
389
386
|
) -> ParseResult<()> {
|
|
390
387
|
// The given pool spans the whole chapter, not one file: a later concept file may reference
|
|
391
388
|
// a named given an earlier one declared. Entries are (owner aggregate name, `Given`).
|
|
392
|
-
|
|
393
|
-
let Some(gated) = super::next_line(file, lines, pos, "Bluebook")? else {
|
|
394
|
-
break;
|
|
395
|
-
};
|
|
389
|
+
while let Some(gated) = super::next_line(file, lines, pos, "Bluebook")? {
|
|
396
390
|
let line = gated.line.number;
|
|
397
391
|
|
|
398
392
|
match gated.row.word {
|
|
@@ -403,11 +397,16 @@ fn parse_body_into(
|
|
|
403
397
|
"vision",
|
|
404
398
|
&gated.args,
|
|
405
399
|
1,
|
|
406
|
-
)?)
|
|
400
|
+
)?);
|
|
407
401
|
}
|
|
408
402
|
"namespace" => {
|
|
409
|
-
bluebook.namespace =
|
|
410
|
-
|
|
403
|
+
bluebook.namespace = Some(super::positional_text(
|
|
404
|
+
file,
|
|
405
|
+
line,
|
|
406
|
+
"namespace",
|
|
407
|
+
&gated.args,
|
|
408
|
+
1,
|
|
409
|
+
)?);
|
|
411
410
|
}
|
|
412
411
|
"formerly_known_as" => {
|
|
413
412
|
bluebook.formerly_known_as = Some(super::positional_text(
|
|
@@ -416,7 +415,7 @@ fn parse_body_into(
|
|
|
416
415
|
"formerly_known_as",
|
|
417
416
|
&gated.args,
|
|
418
417
|
1,
|
|
419
|
-
)?)
|
|
418
|
+
)?);
|
|
420
419
|
}
|
|
421
420
|
// One `Provision` row per named argument, in source order; required keys are
|
|
422
421
|
// checked by Ruby's `Validation#validate_provisions!`.
|
|
@@ -113,6 +113,7 @@ pub struct PendingCommandGiven {
|
|
|
113
113
|
/// Parses a `command "Name" do ... end` body.
|
|
114
114
|
/// `owner` tells a self `reference_to` from a cross-reference. `preconditions` and the `owner_*`
|
|
115
115
|
/// slices hold what the owner declared so far, so declaration order matters.
|
|
116
|
+
#[allow(clippy::too_many_arguments)] // established signature
|
|
116
117
|
pub fn parse_body(
|
|
117
118
|
file: &str,
|
|
118
119
|
lines: &[SourceLine],
|
|
@@ -164,7 +165,13 @@ pub fn parse_body(
|
|
|
164
165
|
owner_entities,
|
|
165
166
|
);
|
|
166
167
|
refuse_duplicate_targets(file, *pos, &command)?;
|
|
167
|
-
super::needs::refuse_undeclared(
|
|
168
|
+
super::needs::refuse_undeclared(
|
|
169
|
+
file,
|
|
170
|
+
*pos,
|
|
171
|
+
&command.name,
|
|
172
|
+
&command.needs,
|
|
173
|
+
&command.attributes,
|
|
174
|
+
)?;
|
|
168
175
|
return Ok((command, pending));
|
|
169
176
|
};
|
|
170
177
|
let line = gated.line.number;
|
|
@@ -177,7 +184,7 @@ pub fn parse_body(
|
|
|
177
184
|
"role",
|
|
178
185
|
&gated.args,
|
|
179
186
|
1,
|
|
180
|
-
)?)
|
|
187
|
+
)?);
|
|
181
188
|
}
|
|
182
189
|
"goal" => {
|
|
183
190
|
command.goal = Some(super::positional_literal_text(
|
|
@@ -186,7 +193,7 @@ pub fn parse_body(
|
|
|
186
193
|
"goal",
|
|
187
194
|
&gated.args,
|
|
188
195
|
1,
|
|
189
|
-
)?)
|
|
196
|
+
)?);
|
|
190
197
|
}
|
|
191
198
|
// A synthesized inline `one_of(...)` closed set is dropped; `CommandBuilder#build`
|
|
192
199
|
// ignores it.
|
|
@@ -218,7 +225,9 @@ pub fn parse_body(
|
|
|
218
225
|
canonical: canonical::apply(&raw),
|
|
219
226
|
});
|
|
220
227
|
}
|
|
221
|
-
"needs" =>
|
|
228
|
+
"needs" => {
|
|
229
|
+
super::needs::declare(file, line, &command.name, &gated.args, &mut command.needs)?;
|
|
230
|
+
}
|
|
222
231
|
"provenance" => {
|
|
223
232
|
let raw = super::named_raw(&gated.args, "from")
|
|
224
233
|
.ok_or_else(|| Diagnostic::new(file, line, "'provenance' requires a from:"))?;
|
|
@@ -283,7 +292,7 @@ fn resolve_implicit_attributes(
|
|
|
283
292
|
for job in jobs {
|
|
284
293
|
match job {
|
|
285
294
|
Job::BareSet(target) => {
|
|
286
|
-
resolve_bare_set(&mut command.attributes, &target, owner_attributes)
|
|
295
|
+
resolve_bare_set(&mut command.attributes, &target, owner_attributes);
|
|
287
296
|
}
|
|
288
297
|
Job::Append { target, fields } => resolve_append_fields(
|
|
289
298
|
&mut command.attributes,
|
|
@@ -544,7 +553,11 @@ fn build_mutation(
|
|
|
544
553
|
}
|
|
545
554
|
// `state(:field)` is the record's own field, neither argument nor literal; classified
|
|
546
555
|
// before the Symbol/literal split.
|
|
547
|
-
let computed = if op == "set" {
|
|
556
|
+
let computed = if op == "set" {
|
|
557
|
+
crate::expr::resolver::arithmetic_text(raw.trim())
|
|
558
|
+
} else {
|
|
559
|
+
None
|
|
560
|
+
};
|
|
548
561
|
let source = match state_ref(raw.trim()) {
|
|
549
562
|
Some(name) => ir::MutationSource::State(name),
|
|
550
563
|
None if computed.is_some() => ir::MutationSource::Expression(computed.unwrap_or_default()),
|
|
@@ -581,13 +594,12 @@ fn build_delegation(
|
|
|
581
594
|
));
|
|
582
595
|
}
|
|
583
596
|
|
|
584
|
-
let fields =
|
|
585
|
-
|
|
597
|
+
let fields = super::named_raw(args, "with").map_or_else(Vec::new, |raw| {
|
|
598
|
+
parse_hash_literal_pairs(raw)
|
|
586
599
|
.into_iter()
|
|
587
600
|
.map(|(k, v)| (k, ruby_value::render(&ruby_value::read(&v))))
|
|
588
|
-
.collect()
|
|
589
|
-
|
|
590
|
-
};
|
|
601
|
+
.collect()
|
|
602
|
+
});
|
|
591
603
|
Ok(ir::Mutation::Delegate { target, fields })
|
|
592
604
|
}
|
|
593
605
|
|
|
@@ -613,15 +625,22 @@ fn build_correction(
|
|
|
613
625
|
));
|
|
614
626
|
}
|
|
615
627
|
|
|
616
|
-
let as_field =
|
|
617
|
-
|
|
618
|
-
|
|
628
|
+
let as_field = super::named_symbol(args, "as").map_or_else(
|
|
629
|
+
|| "nil".to_string(),
|
|
630
|
+
|name| ruby_value::render(&ruby_value::Value::Str(name)),
|
|
631
|
+
);
|
|
632
|
+
let reverses = if super::named_flag(args, "reverses") {
|
|
633
|
+
"true"
|
|
634
|
+
} else {
|
|
635
|
+
"false"
|
|
619
636
|
};
|
|
620
|
-
let reverses = if super::named_flag(args, "reverses") { "true" } else { "false" };
|
|
621
637
|
|
|
622
638
|
let fields = vec![
|
|
623
639
|
("as".to_string(), as_field),
|
|
624
|
-
(
|
|
640
|
+
(
|
|
641
|
+
"reason".to_string(),
|
|
642
|
+
ruby_value::render(&ruby_value::Value::Str(reason)),
|
|
643
|
+
),
|
|
625
644
|
("reverses".to_string(), reverses.to_string()),
|
|
626
645
|
];
|
|
627
646
|
Ok(ir::Mutation::Correction { target, fields })
|
|
@@ -68,7 +68,7 @@ fn parse_operation_body(
|
|
|
68
68
|
|
|
69
69
|
loop {
|
|
70
70
|
let Some(gated) = super::next_line(file, lines, pos, "PortOperation")? else {
|
|
71
|
-
validate_operation(file, pos_line(lines, pos), &operation)?;
|
|
71
|
+
validate_operation(file, pos_line(lines, *pos), &operation)?;
|
|
72
72
|
return Ok(operation);
|
|
73
73
|
};
|
|
74
74
|
let line = gated.line.number;
|
|
@@ -82,7 +82,7 @@ fn parse_operation_body(
|
|
|
82
82
|
"emits" => {
|
|
83
83
|
operation
|
|
84
84
|
.emits
|
|
85
|
-
.push(super::positional_text(file, line, "emits", &gated.args, 1)?)
|
|
85
|
+
.push(super::positional_text(file, line, "emits", &gated.args, 1)?);
|
|
86
86
|
}
|
|
87
87
|
"reference_to" => {
|
|
88
88
|
let target_raw =
|
|
@@ -109,11 +109,8 @@ fn parse_operation_body(
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/// The last consumed line, for diagnostics; 0 when none.
|
|
112
|
-
fn pos_line(lines: &[SourceLine], pos:
|
|
113
|
-
lines
|
|
114
|
-
.get(pos.saturating_sub(1))
|
|
115
|
-
.map(|l| l.number)
|
|
116
|
-
.unwrap_or(0)
|
|
112
|
+
fn pos_line(lines: &[SourceLine], pos: usize) -> usize {
|
|
113
|
+
lines.get(pos.saturating_sub(1)).map_or(0, |l| l.number)
|
|
117
114
|
}
|
|
118
115
|
|
|
119
116
|
/// An inbound operation must emit something. The receiver comes from the routing envelope,
|