hecks 1.4.0 → 1.5.1
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/lib/hecks/adapters/driven/claude_code.rb +65 -0
- data/lib/hecks/adapters/driven/folder.rb +73 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +93 -2
- data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
- data/lib/hecks/adapters/driven/lambda.rb +43 -2
- data/lib/hecks/adapters/driven/local_storage.rb +67 -1
- data/lib/hecks/adapters/driven/memory.rb +13 -13
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
- data/lib/hecks/adapters/driven/prism.rb +46 -7
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +2 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
- data/lib/hecks/behaviors/dsl.rb +58 -0
- data/lib/hecks/behaviors/expectations.rb +168 -7
- data/lib/hecks/behaviors/ir.rb +11 -0
- data/lib/hecks/behaviors/rspec.rb +8 -0
- data/lib/hecks/behaviors/runner.rb +19 -0
- data/lib/hecks/behaviors.rb +8 -0
- data/lib/hecks/bluebook/aggregate.rb +30 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
- data/lib/hecks/bluebook/assembly/build.rb +25 -4
- data/lib/hecks/bluebook/assembly/contract.rb +66 -14
- data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
- data/lib/hecks/bluebook/assembly/marks.rb +143 -14
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
- data/lib/hecks/bluebook/assembly.rb +25 -7
- data/lib/hecks/bluebook/attribute.rb +17 -3
- data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
- data/lib/hecks/bluebook/behaviour/command.rb +33 -3
- data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
- data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
- data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
- data/lib/hecks/bluebook/chapter.rb +23 -0
- data/lib/hecks/bluebook/command.rb +53 -8
- data/lib/hecks/bluebook/domain_port.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
- data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
- data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
- data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
- data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
- data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
- data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
- data/lib/hecks/bluebook/entity.rb +29 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
- data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
- data/lib/hecks/bluebook/expression/resolver.rb +320 -77
- data/lib/hecks/bluebook/hexagon.rb +34 -0
- data/lib/hecks/bluebook/lifecycle.rb +11 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
- data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
- data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator.rb +180 -84
- data/lib/hecks/bluebook/model_check.rb +268 -24
- data/lib/hecks/bluebook/pattern_subset.rb +23 -1
- data/lib/hecks/bluebook/process_manager.rb +13 -0
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +44 -0
- data/lib/hecks/bluebook/query.rb +27 -0
- data/lib/hecks/bluebook/read_model.rb +21 -1
- data/lib/hecks/bluebook/reference.rb +21 -8
- data/lib/hecks/bluebook/smoke_test.rb +29 -6
- data/lib/hecks/bluebook/synthesizer.rb +34 -0
- data/lib/hecks/bluebook/translation.rb +30 -1
- data/lib/hecks/bluebook/value_object.rb +23 -5
- data/lib/hecks/bluebook.rb +3 -4
- data/lib/hecks/codemod.rb +107 -20
- data/lib/hecks/construct.rb +15 -1
- data/lib/hecks/corpus.rb +146 -25
- data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/doc/reference.rb +185 -16
- data/lib/hecks/embryonaut_bluebook.rb +32 -9
- data/lib/hecks/facade/handle.rb +76 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
- data/lib/hecks/forms/field_shape.rb +3 -0
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/port_argument.rb +12 -0
- data/lib/hecks/forms/query_form_renderer.rb +63 -0
- data/lib/hecks/forms/record_renderer.rb +58 -0
- data/lib/hecks/forms/record_table.rb +27 -0
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +10 -0
- data/lib/hecks/fqn.rb +58 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +43 -20
- data/lib/hecks/freezer.rb +17 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
- data/lib/hecks/fuzzing/combination_miner.rb +59 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
- data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
- data/lib/hecks/fuzzing/differential.rb +34 -0
- data/lib/hecks/fuzzing/domain_generator.rb +188 -11
- data/lib/hecks/fuzzing/era_boundary.rb +45 -15
- data/lib/hecks/fuzzing/form_census.rb +86 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
- data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
- data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
- data/lib/hecks/fuzzing/properties/guards.rb +44 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
- data/lib/hecks/fuzzing/properties/querying.rb +68 -14
- data/lib/hecks/fuzzing/properties.rb +24 -15
- data/lib/hecks/fuzzing/qa_settings.rb +12 -0
- data/lib/hecks/fuzzing/replay.rb +137 -29
- data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
- data/lib/hecks/fuzzing/self_consistency.rb +189 -40
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
- data/lib/hecks/fuzzing/shrinker.rb +114 -2
- data/lib/hecks/fuzzing/structural_skips.rb +18 -4
- data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
- data/lib/hecks/fuzzing/value_generator.rb +98 -10
- data/lib/hecks/grammar/evolve.rb +178 -2
- data/lib/hecks/grammar.rb +46 -0
- data/lib/hecks/ir.rb +38 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/literal.rb +32 -0
- data/lib/hecks/naming.rb +88 -7
- data/lib/hecks/ports/access_control.rb +5 -10
- data/lib/hecks/ports/authorization.rb +3 -6
- data/lib/hecks/ports/identity_assignment.rb +1 -2
- data/lib/hecks/ports/identity_resolution.rb +1 -2
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
- data/lib/hecks/ports/query/in_memory.rb +39 -6
- data/lib/hecks/ports/query/ordering.rb +15 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +43 -8
- data/lib/hecks/projections/deploy/fargate.rb +666 -0
- data/lib/hecks/projections/deploy/lambda.rb +2423 -0
- data/lib/hecks/projections/deploy/shared.rb +624 -0
- data/lib/hecks/projections/diagrams.rb +243 -7
- data/lib/hecks/projections/glossary/html.rb +88 -0
- data/lib/hecks/projections/glossary/markdown.rb +47 -0
- data/lib/hecks/projections/glossary/mermaid.rb +48 -0
- data/lib/hecks/projections/glossary/sentences.rb +105 -0
- data/lib/hecks/projections/glossary.rb +161 -19
- data/lib/hecks/projections/model/deviations.rb +44 -0
- data/lib/hecks/projections/model.rb +51 -1
- data/lib/hecks/projections/oidc.rb +40 -3
- data/lib/hecks/projections/parser_table.rb +49 -8
- data/lib/hecks/projections/reference.rb +12 -0
- data/lib/hecks/projections/rust_vocabulary.rb +219 -16
- data/lib/hecks/projections/shape.rb +8 -0
- data/lib/hecks/projections/statements.rb +63 -16
- data/lib/hecks/projections/vocabulary.rb +17 -0
- data/lib/hecks/projections.rb +3 -0
- data/lib/hecks/projector/cli_projector.rb +218 -10
- data/lib/hecks/projector/docs_projector.rb +145 -19
- data/lib/hecks/projector/exporter.rb +65 -11
- data/lib/hecks/projector/ir_projector.rb +6 -0
- data/lib/hecks/projector/narrate_projector.rb +136 -15
- data/lib/hecks/projector/target.rb +47 -10
- data/lib/hecks/projector.rb +101 -19
- data/lib/hecks/query_ir.rb +47 -0
- data/lib/hecks/query_specification/common/null_policy.rb +5 -3
- data/lib/hecks/rendering.rb +6 -0
- data/lib/hecks/router/namespace_installer.rb +13 -0
- data/lib/hecks/router.rb +55 -0
- data/lib/hecks/runtime/aggregate_lock.rb +9 -0
- data/lib/hecks/runtime/boot_gates.rb +18 -0
- data/lib/hecks/runtime/caller.rb +32 -0
- data/lib/hecks/runtime/capability_graph.rb +11 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
- data/lib/hecks/runtime/command_interpreter.rb +42 -17
- data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
- data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
- data/lib/hecks/runtime/command_rules/references.rb +118 -28
- data/lib/hecks/runtime/dependency_planning.rb +45 -0
- data/lib/hecks/runtime/dispatcher.rb +28 -50
- data/lib/hecks/runtime/entity_element.rb +161 -8
- data/lib/hecks/runtime/entity_interpreter.rb +44 -9
- data/lib/hecks/runtime/errors.rb +18 -4
- data/lib/hecks/runtime/event.rb +10 -5
- data/lib/hecks/runtime/identity.rb +71 -3
- data/lib/hecks/runtime/instance.rb +67 -7
- data/lib/hecks/runtime/interpreting.rb +13 -5
- data/lib/hecks/runtime/invocation.rb +118 -36
- data/lib/hecks/runtime/loader.rb +94 -8
- data/lib/hecks/runtime/outbox.rb +145 -7
- data/lib/hecks/runtime/policy_interpreter.rb +22 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
- data/lib/hecks/runtime/query_interpreter.rb +40 -12
- data/lib/hecks/runtime/reaction_invocation.rb +53 -8
- data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
- data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
- data/lib/hecks/runtime/reference_hop.rb +42 -0
- data/lib/hecks/runtime/refusal_wording.rb +50 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
- data/lib/hecks/runtime/registry/verification.rb +119 -4
- data/lib/hecks/runtime/registry.rb +157 -4
- data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
- data/lib/hecks/runtime/routing.rb +27 -2
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
- data/lib/hecks/runtime/saga_interpreter.rb +27 -13
- data/lib/hecks/runtime/tenant_check.rb +26 -6
- data/lib/hecks/runtime/tenant_scope.rb +18 -0
- data/lib/hecks/runtime/value/coercion.rb +255 -33
- data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
- data/lib/hecks/runtime/value.rb +50 -0
- data/lib/hecks/runtime.rb +32 -0
- data/lib/hecks/storehouse.rb +305 -9
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +79 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
- metadata +14 -5
- data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
- data/lib/hecks/deprecation.rb +0 -95
- data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
|
@@ -188,10 +188,10 @@ module Hecks
|
|
|
188
188
|
# storehouse-kernel files, byte-identical text) -- `.start_with?(`/
|
|
189
189
|
# `.end_with?(` matched none of this grammar's known suffixes, so
|
|
190
190
|
# both fell through to the `Lookup` catch-all and crashed with the
|
|
191
|
-
#
|
|
192
|
-
# Integer` shape
|
|
193
|
-
# real dispatch (not validate), not inferred. Two
|
|
194
|
-
# types rather than one `mode:`-keyed struct (the `BlockPredicate`/
|
|
191
|
+
# same `TypeError: no implicit conversion of Symbol into
|
|
192
|
+
# Integer` shape any unsupported suffix produces here, confirmed
|
|
193
|
+
# live via a real dispatch (not validate), not inferred. Two
|
|
194
|
+
# separate node types rather than one `mode:`-keyed struct (the `BlockPredicate`/
|
|
195
195
|
# `SignTest` precedent) -- `start_with?`/`end_with?` aren't two
|
|
196
196
|
# spellings of the same test the way `all?`/`any?`/`none?` are (one
|
|
197
197
|
# Array-aggregation family) or `positive?`/`negative?`/`zero?` are
|
|
@@ -206,6 +206,23 @@ module Hecks
|
|
|
206
206
|
|
|
207
207
|
module_function
|
|
208
208
|
|
|
209
|
+
# Parses and interprets `expr` in one step, without going through
|
|
210
|
+
# `Evaluator`'s own boolean/comparison grammar or its `ast_cache` —
|
|
211
|
+
# for a caller that wants this leaf grammar's own raw value (an
|
|
212
|
+
# Integer, a String, an Array, ...), not a boolean.
|
|
213
|
+
#
|
|
214
|
+
# @param expr [String] the dotted/arithmetic leaf expression to
|
|
215
|
+
# resolve
|
|
216
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
217
|
+
# an unqualified name may resolve against
|
|
218
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
219
|
+
# values, checked before `state`
|
|
220
|
+
# @return [Object] whatever `expr` resolves to: an Integer, Float,
|
|
221
|
+
# String, `true`/`false`, `nil`, or Array, depending on `expr`'s
|
|
222
|
+
# own leaf grammar
|
|
223
|
+
# @raise [EvaluationError] if `expr` names an attribute or argument
|
|
224
|
+
# neither `attrs` nor `state` has, or if an operation in `expr`
|
|
225
|
+
# is applied to a value of the wrong type
|
|
209
226
|
def resolve(expr, state, attrs)
|
|
210
227
|
interpret(parse(expr), state, attrs)
|
|
211
228
|
end
|
|
@@ -218,6 +235,15 @@ module Hecks
|
|
|
218
235
|
# branch is already one line; splitting the table into smaller
|
|
219
236
|
# methods would not shrink any single check, only hide the
|
|
220
237
|
# precedence order this method's own line-by-line sequence is.
|
|
238
|
+
#
|
|
239
|
+
# @param expr [String] the leaf expression text to parse
|
|
240
|
+
# @return [Object] the parsed leaf node — one of this module's own
|
|
241
|
+
# Struct types (`IntegerLiteral`, `FloatLiteral`, `StringLiteral`,
|
|
242
|
+
# `BoolLiteral`, `NilLiteral`, `ArrayLiteral`, `Addition`,
|
|
243
|
+
# `SignTest`, `Empty`, `ToS`, `Modulo`, `Size`, `MatchesRegex`,
|
|
244
|
+
# `Presence`, `Assignment`, `Split`, `First`, `Last`,
|
|
245
|
+
# `StartsWith`, `EndsWith`, `BlockPredicate`, `Find`), chosen by
|
|
246
|
+
# `expr`'s own shape, or `Lookup` when nothing else matches
|
|
221
247
|
# rubocop:disable-next Metrics/AbcSize
|
|
222
248
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
223
249
|
# rubocop:disable-next Metrics/MethodLength
|
|
@@ -287,6 +313,13 @@ module Hecks
|
|
|
287
313
|
Lookup.new(path: expr)
|
|
288
314
|
end
|
|
289
315
|
|
|
316
|
+
# Builds the `SignTest` node for a matched `.positive?`/
|
|
317
|
+
# `.negative?`/`.zero?`-style suffix.
|
|
318
|
+
#
|
|
319
|
+
# @param parts [Array(String, String)] the `[receiver_text,
|
|
320
|
+
# test_name]` pair `match_suffix` returns
|
|
321
|
+
# @return [SignTest] the parsed sign-test node, its `receiver`
|
|
322
|
+
# itself parsed
|
|
290
323
|
def sign_test_node(parts)
|
|
291
324
|
receiver, test = parts
|
|
292
325
|
symbol = SIGN_TEST_OPERATORS.fetch(test)
|
|
@@ -301,6 +334,21 @@ module Hecks
|
|
|
301
334
|
# exhaust; the `else` backstop's own comment explains why a
|
|
302
335
|
# missing arm is a bug this method is built to make loud, not
|
|
303
336
|
# quiet.
|
|
337
|
+
#
|
|
338
|
+
# @param node [Object] a node `parse` produced (one of this
|
|
339
|
+
# module's own Struct types) or built directly by a caller
|
|
340
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
341
|
+
# a `Lookup` node may resolve against
|
|
342
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
343
|
+
# values, checked before `state`
|
|
344
|
+
# @return [Object] the value `node` resolves to: an Integer, Float,
|
|
345
|
+
# String, `true`/`false`, `nil`, or Array, depending on `node`'s
|
|
346
|
+
# own type
|
|
347
|
+
# @raise [EvaluationError] if `node` is not one of the handled
|
|
348
|
+
# types, or if a helper it dispatches to (`add`, `apply_sign_test`,
|
|
349
|
+
# `matches_regex?`, `split_value`, `last_of`, `first_of`,
|
|
350
|
+
# `starts_with?`, `ends_with?`, `apply_modulo`, `size_of`,
|
|
351
|
+
# `emptiness_of`, `lookup`) refuses its operand
|
|
304
352
|
# rubocop:disable-next Metrics/AbcSize
|
|
305
353
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
306
354
|
# rubocop:disable-next Metrics/MethodLength
|
|
@@ -368,6 +416,11 @@ module Hecks
|
|
|
368
416
|
# `Value#to_h`'d first) is present regardless of what its own
|
|
369
417
|
# inner value holds, matching how every VO-typed field in this
|
|
370
418
|
# corpus is actually shaped once set at all.
|
|
419
|
+
#
|
|
420
|
+
# @param value [Object] the already-interpreted receiver value
|
|
421
|
+
# @return [Boolean] `true` for `nil`, `false`, or an empty
|
|
422
|
+
# String/Array/Hash (a to_h-able value is converted first);
|
|
423
|
+
# `false` for anything else
|
|
371
424
|
def blank?(value)
|
|
372
425
|
return true if value.nil? || value == false
|
|
373
426
|
|
|
@@ -392,6 +445,18 @@ module Hecks
|
|
|
392
445
|
# `Modulo`'s own parse rule above calling a same-named
|
|
393
446
|
# `match_call` that has the identical cross-module problem --
|
|
394
447
|
# found live while building this, not assumed).
|
|
448
|
+
#
|
|
449
|
+
# @param receiver_value [String, Symbol, Integer, Float, nil] the
|
|
450
|
+
# already-interpreted receiver value
|
|
451
|
+
# @param pattern [String] the regex source, exactly as it appeared
|
|
452
|
+
# between the `/.../` delimiters
|
|
453
|
+
# @param flags [String] the suffix flag letters after the closing
|
|
454
|
+
# `/`; `"i"` enables `Regexp::IGNORECASE`, `"m"` `MULTILINE`,
|
|
455
|
+
# `"x"` `EXTENDED`
|
|
456
|
+
# @return [Boolean] whether `pattern` matches `receiver_value`'s
|
|
457
|
+
# string form
|
|
458
|
+
# @raise [EvaluationError] if `receiver_value` is not a scalar, or
|
|
459
|
+
# `pattern` is not a valid regex
|
|
395
460
|
def matches_regex?(receiver_value, pattern, flags)
|
|
396
461
|
text = case receiver_value
|
|
397
462
|
when String, Symbol, Integer, Float then receiver_value.to_s
|
|
@@ -421,6 +486,11 @@ module Hecks
|
|
|
421
486
|
# Splits on top-level commas only — quote-aware and depth-aware,
|
|
422
487
|
# the same discipline `split_addition` already applies, so a
|
|
423
488
|
# nested array or a comma inside a string element stays whole.
|
|
489
|
+
#
|
|
490
|
+
# @param expr [String] the leaf expression text
|
|
491
|
+
# @return [Array<String>, nil] the unparsed source text of each
|
|
492
|
+
# element, in order (`[]` for `"[]"`), or `nil` when `expr` is
|
|
493
|
+
# not bracketed
|
|
424
494
|
def array_elements(expr)
|
|
425
495
|
return nil unless expr.start_with?("[") && expr.end_with?("]")
|
|
426
496
|
|
|
@@ -466,6 +536,11 @@ module Hecks
|
|
|
466
536
|
# downstream chess domain's castling given; the evaluator's own
|
|
467
537
|
# top_level_index has counted braces since its own version of this
|
|
468
538
|
# exact lesson.
|
|
539
|
+
#
|
|
540
|
+
# @param expr [String] the leaf expression text
|
|
541
|
+
# @return [Array(String, String), nil] the `[left, right]` operand
|
|
542
|
+
# text around the first top-level `+`, or `nil` when `expr` has
|
|
543
|
+
# none
|
|
469
544
|
def split_addition(expr)
|
|
470
545
|
depth = 0
|
|
471
546
|
quote = nil
|
|
@@ -479,12 +554,13 @@ module Hecks
|
|
|
479
554
|
# comment already names, a third time (found live via the
|
|
480
555
|
# type-directed bounded-exhaustive expression generator,
|
|
481
556
|
# Phase 7 of the equivalence-gap plan): `ArrayLiteral` can
|
|
482
|
-
# appear as a general sub-expression
|
|
483
|
-
# `.include?`'s own haystack, so
|
|
484
|
-
#
|
|
485
|
-
#
|
|
486
|
-
#
|
|
487
|
-
#
|
|
557
|
+
# appear as a general sub-expression, not only as
|
|
558
|
+
# `.include?`'s own haystack, so without counting `[`/`]`
|
|
559
|
+
# toward depth here too, an array element containing its own
|
|
560
|
+
# top-level `+` (`[0, 0 + 0]`) reads as this expression's own
|
|
561
|
+
# addition split point -- tearing the whole receiver before
|
|
562
|
+
# `.all?`/`.any?`/etc. in half before `parse_block_opener`
|
|
563
|
+
# ever sees it as one atomic leaf.
|
|
488
564
|
elsif ["(", "{", "["].include?(char)
|
|
489
565
|
depth += 1
|
|
490
566
|
elsif [")", "}", "]"].include?(char)
|
|
@@ -503,6 +579,16 @@ module Hecks
|
|
|
503
579
|
# it. A Float sum that is not finite is the same fault (C3.4).
|
|
504
580
|
INT64_RANGE = (-(2**63))..((2**63) - 1)
|
|
505
581
|
|
|
582
|
+
# Adds two already-interpreted numeric operands, refusing an
|
|
583
|
+
# out-of-range result rather than letting it wrap or promote
|
|
584
|
+
# silently.
|
|
585
|
+
#
|
|
586
|
+
# @param left [Integer, Float] the left operand
|
|
587
|
+
# @param right [Integer, Float] the right operand
|
|
588
|
+
# @return [Integer, Float] `left + right`
|
|
589
|
+
# @raise [EvaluationError] if either operand is not numeric, or the
|
|
590
|
+
# Integer sum falls outside signed 64-bit range, or the Float sum
|
|
591
|
+
# is not finite
|
|
506
592
|
def add(left, right)
|
|
507
593
|
lhs = require_number(left, "addition")
|
|
508
594
|
rhs = require_number(right, "addition")
|
|
@@ -517,6 +603,12 @@ module Hecks
|
|
|
517
603
|
raise EvaluationError, "addition overflowed: #{lhs} + #{rhs} is not a finite number"
|
|
518
604
|
end
|
|
519
605
|
|
|
606
|
+
# Reports whether `expr` is a whole string literal — `"` or `'`
|
|
607
|
+
# delimited, both ends.
|
|
608
|
+
#
|
|
609
|
+
# @param expr [String] the leaf expression text
|
|
610
|
+
# @return [Boolean] whether `expr` opens and closes with matching
|
|
611
|
+
# quote characters
|
|
520
612
|
def quoted?(expr)
|
|
521
613
|
return false if expr.length < 2
|
|
522
614
|
|
|
@@ -530,67 +622,111 @@ module Hecks
|
|
|
530
622
|
# which admit the same set for the same reason.
|
|
531
623
|
SIZED_TYPES = Hecks::Vocabulary.fetch("SizedType")
|
|
532
624
|
|
|
625
|
+
# Counts `value`'s elements or characters, for `.length`/`.size`.
|
|
626
|
+
#
|
|
627
|
+
# @param value [Array, String, Hash] the already-interpreted
|
|
628
|
+
# receiver value
|
|
629
|
+
# @return [Integer] the number of elements (Array/Hash) or
|
|
630
|
+
# characters (String) `value` holds
|
|
631
|
+
# @raise [EvaluationError] if `value` is not an Array, String, or
|
|
632
|
+
# Hash
|
|
533
633
|
def size_of(value)
|
|
534
634
|
return value.size if value.is_a?(Array) || value.is_a?(String) || value.is_a?(Hash)
|
|
535
635
|
|
|
536
636
|
raise EvaluationError, "size expects a list or string, got #{describe(value)}"
|
|
537
637
|
end
|
|
538
638
|
|
|
639
|
+
# Reports whether `value` holds no elements or characters, for
|
|
640
|
+
# `.empty?`.
|
|
641
|
+
#
|
|
642
|
+
# @param value [Array, String, Hash] the already-interpreted
|
|
643
|
+
# receiver value
|
|
644
|
+
# @return [Boolean] whether `value` holds no elements or characters
|
|
645
|
+
# @raise [EvaluationError] if `value` is not an Array, String, or
|
|
646
|
+
# Hash
|
|
539
647
|
def emptiness_of(value)
|
|
540
648
|
return value.empty? if value.is_a?(Array) || value.is_a?(String) || value.is_a?(Hash)
|
|
541
649
|
|
|
542
650
|
raise EvaluationError, "empty? expects a list or string, got #{describe(value)}"
|
|
543
651
|
end
|
|
544
652
|
|
|
545
|
-
# `.split("SEP")` -- vendored
|
|
546
|
-
# own comment above. Only a
|
|
547
|
-
# split -- unlike `.length`/`.size
|
|
548
|
-
# meaningful over Array/Hash too,
|
|
549
|
-
# method in the corpus's own usage
|
|
550
|
-
# pass splits a Phrase's own string
|
|
653
|
+
# Splits `value` on `separator`, for `.split("SEP")` -- vendored
|
|
654
|
+
# addition, see the `Split` struct's own comment above. Only a
|
|
655
|
+
# String receiver makes sense to split -- unlike `.length`/`.size`/
|
|
656
|
+
# `.empty?`, which are already meaningful over Array/Hash too,
|
|
657
|
+
# `.split` is a String-only method in the corpus's own usage
|
|
658
|
+
# (every occurrence found this pass splits a Phrase's own string
|
|
659
|
+
# value).
|
|
660
|
+
#
|
|
661
|
+
# @param value [String] the already-interpreted receiver value
|
|
662
|
+
# @param separator [String] the literal separator text
|
|
663
|
+
# @return [Array<String>] `value` split on `separator`, Ruby's own
|
|
664
|
+
# `String#split` semantics
|
|
665
|
+
# @raise [EvaluationError] if `value` is not a String
|
|
551
666
|
def split_value(value, separator)
|
|
552
667
|
raise EvaluationError, "split expects a string, got #{describe(value)}" unless value.is_a?(String)
|
|
553
668
|
|
|
554
669
|
value.split(separator)
|
|
555
670
|
end
|
|
556
671
|
|
|
557
|
-
#
|
|
558
|
-
# comment above. Duck-typed
|
|
559
|
-
# hard-coding Array -- the one
|
|
560
|
-
# (`Query::Phrase`'s
|
|
561
|
-
# `Split`-produced Array,
|
|
562
|
-
# Array-specific, and this
|
|
563
|
-
# duck-typed-over-a-known-set
|
|
564
|
-
# narrower rule than the method
|
|
672
|
+
# Returns `value`'s last element, for `.last` -- vendored
|
|
673
|
+
# addition, see the `Last` struct's own comment above. Duck-typed
|
|
674
|
+
# on `respond_to?(:last)` rather than hard-coding Array -- the one
|
|
675
|
+
# corpus usage found this pass (`Query::Phrase`'s
|
|
676
|
+
# `.split("::").last`) always receives a `Split`-produced Array,
|
|
677
|
+
# but nothing about `.last` itself is Array-specific, and this
|
|
678
|
+
# matches `Empty`/`Size`'s own duck-typed-over-a-known-set
|
|
679
|
+
# precedent without inventing a narrower rule than the method
|
|
680
|
+
# needs.
|
|
681
|
+
#
|
|
682
|
+
# @param value [#last] the already-interpreted receiver value
|
|
683
|
+
# @return [Object] `value.last`
|
|
684
|
+
# @raise [EvaluationError] if `value` does not respond to `:last`
|
|
565
685
|
def last_of(value)
|
|
566
686
|
return value.last if value.respond_to?(:last)
|
|
567
687
|
|
|
568
688
|
raise EvaluationError, "last expects a list, got #{describe(value)}"
|
|
569
689
|
end
|
|
570
690
|
|
|
571
|
-
# `.first` -- see the
|
|
572
|
-
# `last_of` with the one
|
|
573
|
-
# reasoning.
|
|
691
|
+
# Returns `value`'s first element, for `.first` -- see the
|
|
692
|
+
# `First` struct's own comment above. `last_of` with the one
|
|
693
|
+
# method swapped, same duck-typed reasoning.
|
|
694
|
+
#
|
|
695
|
+
# @param value [#first] the already-interpreted receiver value
|
|
696
|
+
# @return [Object] `value.first`
|
|
697
|
+
# @raise [EvaluationError] if `value` does not respond to `:first`
|
|
574
698
|
def first_of(value)
|
|
575
699
|
return value.first if value.respond_to?(:first)
|
|
576
700
|
|
|
577
701
|
raise EvaluationError, "first expects a list, got #{describe(value)}"
|
|
578
702
|
end
|
|
579
703
|
|
|
704
|
+
# Reports whether `value` starts with `substring`, for
|
|
580
705
|
# `.start_with?("prefix")` -- vendored addition, see the
|
|
581
706
|
# `StartsWith` struct's own comment above. String-only, same
|
|
582
707
|
# reasoning as `.split` above -- every corpus usage found this
|
|
583
708
|
# pass (`Params`'s own JSON-object-shape invariant) receives a
|
|
584
709
|
# plain String field.
|
|
710
|
+
#
|
|
711
|
+
# @param value [String] the already-interpreted receiver value
|
|
712
|
+
# @param substring [String] the literal prefix text
|
|
713
|
+
# @return [Boolean] whether `value` starts with `substring`
|
|
714
|
+
# @raise [EvaluationError] if `value` is not a String
|
|
585
715
|
def starts_with?(value, substring)
|
|
586
716
|
raise EvaluationError, "start_with? expects a string, got #{describe(value)}" unless value.is_a?(String)
|
|
587
717
|
|
|
588
718
|
value.start_with?(substring)
|
|
589
719
|
end
|
|
590
720
|
|
|
721
|
+
# Reports whether `value` ends with `substring`, for
|
|
591
722
|
# `.end_with?("suffix")` -- vendored addition, see the `EndsWith`
|
|
592
723
|
# struct's own comment above. Same String-only reasoning as
|
|
593
724
|
# `start_with?` immediately above.
|
|
725
|
+
#
|
|
726
|
+
# @param value [String] the already-interpreted receiver value
|
|
727
|
+
# @param substring [String] the literal suffix text
|
|
728
|
+
# @return [Boolean] whether `value` ends with `substring`
|
|
729
|
+
# @raise [EvaluationError] if `value` is not a String
|
|
594
730
|
def ends_with?(value, substring)
|
|
595
731
|
raise EvaluationError, "end_with? expects a string, got #{describe(value)}" unless value.is_a?(String)
|
|
596
732
|
|
|
@@ -602,6 +738,13 @@ module Hecks
|
|
|
602
738
|
# holds this equal to the language.
|
|
603
739
|
TO_STRING_TYPES = Hecks::Vocabulary.fetch("ToStringType")
|
|
604
740
|
|
|
741
|
+
# Coerces `value` to its string form, for `.to_s`.
|
|
742
|
+
#
|
|
743
|
+
# @param value [String, Integer, Float, Boolean, nil] the
|
|
744
|
+
# already-interpreted receiver value
|
|
745
|
+
# @return [String] `value` unchanged if already a String,
|
|
746
|
+
# `value.to_s` for a numeric or boolean, or `""` for `nil`
|
|
747
|
+
# @raise [EvaluationError] if `value` is not one of the above
|
|
605
748
|
def string_of(value)
|
|
606
749
|
case value
|
|
607
750
|
when String then value
|
|
@@ -612,6 +755,16 @@ module Hecks
|
|
|
612
755
|
end
|
|
613
756
|
end
|
|
614
757
|
|
|
758
|
+
# Splits `expr` at the first of `suffixes` it ends with — the
|
|
759
|
+
# shared lookup behind every `.positive?`/`.negative?`/`.zero?`-
|
|
760
|
+
# style sign test.
|
|
761
|
+
#
|
|
762
|
+
# @param expr [String] the leaf expression text
|
|
763
|
+
# @param suffixes [Array<String>] the admitted suffix names
|
|
764
|
+
# (without the leading `.`), tried in order
|
|
765
|
+
# @return [Array(String, String), nil] the `[receiver_text,
|
|
766
|
+
# matched_suffix]` pair for the first matching suffix, or `nil`
|
|
767
|
+
# if none matches
|
|
615
768
|
def match_suffix(expr, suffixes)
|
|
616
769
|
suffixes.each do |suffix|
|
|
617
770
|
marker = ".#{suffix}"
|
|
@@ -620,6 +773,14 @@ module Hecks
|
|
|
620
773
|
nil
|
|
621
774
|
end
|
|
622
775
|
|
|
776
|
+
# Applies `node`'s comparison (`positive?`/`negative?`/`zero?`)
|
|
777
|
+
# against the literal `0`, reusing `Evaluator.apply`'s own
|
|
778
|
+
# comparison primitives rather than re-deriving them by hand.
|
|
779
|
+
#
|
|
780
|
+
# @param node [SignTest] the sign-test node being evaluated
|
|
781
|
+
# @param value [Object] the already-interpreted receiver value
|
|
782
|
+
# @return [Boolean] whether `value` passes `node`'s own sign test
|
|
783
|
+
# @raise [EvaluationError] if `value` is not numeric
|
|
623
784
|
def apply_sign_test(node, value)
|
|
624
785
|
number = numeric(value)
|
|
625
786
|
raise EvaluationError, "#{node.test} expects a number, got #{describe(value)}" unless number
|
|
@@ -671,6 +832,14 @@ module Hecks
|
|
|
671
832
|
# call to the true final `)`; for chaining, the leftmost
|
|
672
833
|
# occurrence's close lands short and is rejected, so the next
|
|
673
834
|
# occurrence (the true outermost call) is tried instead.
|
|
835
|
+
#
|
|
836
|
+
# @param expr [String] the leaf expression text
|
|
837
|
+
# @param marker [String] the call-opening text to search for, such
|
|
838
|
+
# as `".modulo("`
|
|
839
|
+
# @return [Array(String, String), nil] the `[receiver_text,
|
|
840
|
+
# argument_text]` pair for the outermost occurrence of `marker`
|
|
841
|
+
# whose matching close paren reaches `expr`'s last character, or
|
|
842
|
+
# `nil` if none does
|
|
674
843
|
def match_call(expr, marker)
|
|
675
844
|
start = 0
|
|
676
845
|
while (index = expr.index(marker, start))
|
|
@@ -682,10 +851,17 @@ module Hecks
|
|
|
682
851
|
nil
|
|
683
852
|
end
|
|
684
853
|
|
|
685
|
-
# `
|
|
686
|
-
#
|
|
687
|
-
#
|
|
688
|
-
#
|
|
854
|
+
# Finds the index of the `)` that closes the `(` already consumed
|
|
855
|
+
# by the caller. `matching_brace` (resolver/block_predicates.rb)'s
|
|
856
|
+
# own twin, one bracket pair over: `start` is the index just past
|
|
857
|
+
# the opening `(` already consumed by the caller (depth starts at
|
|
858
|
+
# 1, not 0, for the same reason).
|
|
859
|
+
#
|
|
860
|
+
# @param expr [String] the source text to scan
|
|
861
|
+
# @param start [Integer] the index just past the already-consumed
|
|
862
|
+
# opening `(`
|
|
863
|
+
# @return [Integer, nil] the index of the matching `)`, or `nil` if
|
|
864
|
+
# `expr` has no balanced close from `start` onward
|
|
689
865
|
def matching_paren(expr, start)
|
|
690
866
|
depth = 1
|
|
691
867
|
quote = nil
|
|
@@ -724,6 +900,15 @@ module Hecks
|
|
|
724
900
|
# operands down to Integer first was pure data loss with no
|
|
725
901
|
# purpose: `7.5.modulo(2.5)` silently became `7 % 2` (`1`)
|
|
726
902
|
# instead of the real `0.0`.
|
|
903
|
+
#
|
|
904
|
+
# @param receiver_value [Object] the already-interpreted receiver
|
|
905
|
+
# value
|
|
906
|
+
# @param divisor_value [Object] the already-interpreted divisor
|
|
907
|
+
# value
|
|
908
|
+
# @return [Integer, Float] `receiver % divisor`, on the coerced
|
|
909
|
+
# numeric operands
|
|
910
|
+
# @raise [EvaluationError] if either operand is not numeric, or the
|
|
911
|
+
# coerced divisor is zero
|
|
727
912
|
def apply_modulo(receiver_value, divisor_value)
|
|
728
913
|
receiver = require_number(receiver_value, "modulo")
|
|
729
914
|
divisor = require_number(divisor_value, "modulo")
|
|
@@ -732,6 +917,20 @@ module Hecks
|
|
|
732
917
|
receiver % divisor
|
|
733
918
|
end
|
|
734
919
|
|
|
920
|
+
# Resolves a `Lookup` node's dotted path against `attrs` (checked
|
|
921
|
+
# first) and `state`, unwrapping a single-field value object's
|
|
922
|
+
# result to its own scalar.
|
|
923
|
+
#
|
|
924
|
+
# @param expr [String] the dotted (or bare) attribute path, such as
|
|
925
|
+
# `"customer.status"`
|
|
926
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
927
|
+
# the path's root name may resolve against
|
|
928
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
929
|
+
# values, checked before `state`
|
|
930
|
+
# @return [Object] the resolved value, unwrapped by `unwrap_scalar`
|
|
931
|
+
# @raise [EvaluationError] if the path's root name is in neither
|
|
932
|
+
# `attrs` nor `state`, or a later segment cannot be read from the
|
|
933
|
+
# value it navigates onto
|
|
735
934
|
def lookup(expr, state, attrs)
|
|
736
935
|
return unwrap_scalar(fetch(expr, state, attrs)) unless expr.include?(".")
|
|
737
936
|
|
|
@@ -748,6 +947,16 @@ module Hecks
|
|
|
748
947
|
# which spelling answers — a bare `||` between the two would
|
|
749
948
|
# treat a genuinely-held `false` the same as an absent key and
|
|
750
949
|
# fall through to the other spelling, landing on `nil`.
|
|
950
|
+
#
|
|
951
|
+
# @param value [Object] the already-resolved starting value; `nil`
|
|
952
|
+
# once any segment lands on a value that does not respond to
|
|
953
|
+
# `#[]`
|
|
954
|
+
# @param segments [Array<String>] the dotted path's remaining
|
|
955
|
+
# segments, in order
|
|
956
|
+
# @return [Object, nil] the value reached by walking every segment,
|
|
957
|
+
# or `nil` if a segment lands on a value with no `#[]`
|
|
958
|
+
# @raise [EvaluationError] if a segment cannot be read from an
|
|
959
|
+
# Array-like value (a non-Integer/Range index)
|
|
751
960
|
def walk_path(value, segments)
|
|
752
961
|
segments.reduce(value) do |current, segment|
|
|
753
962
|
break nil unless current.respond_to?(:[])
|
|
@@ -764,10 +973,10 @@ module Hecks
|
|
|
764
973
|
# or a `list_of` attribute) — Array#[] demands an
|
|
765
974
|
# Integer/Range and raises a raw TypeError for a String
|
|
766
975
|
# segment ("no implicit conversion of String into
|
|
767
|
-
# Integer")
|
|
768
|
-
# sublanguage's own refusal
|
|
769
|
-
#
|
|
770
|
-
#
|
|
976
|
+
# Integer"). This rescue catches it and re-raises as this
|
|
977
|
+
# sublanguage's own refusal ("this predicate doesn't apply
|
|
978
|
+
# here"), rather than letting the raw TypeError cross this
|
|
979
|
+
# sublanguage's own refusal boundary and crash the runtime.
|
|
771
980
|
raise EvaluationError,
|
|
772
981
|
"cannot read #{segment.inspect} from #{describe(current)}"
|
|
773
982
|
end
|
|
@@ -775,6 +984,7 @@ module Hecks
|
|
|
775
984
|
end
|
|
776
985
|
end
|
|
777
986
|
|
|
987
|
+
# Unwraps a single-field value object to its own scalar, so
|
|
778
988
|
# `field == "literal"` -- vendored addition, not (yet) upstream
|
|
779
989
|
# hecks (migration plan task 8): the third-most pervasive
|
|
780
990
|
# dispatch-time gap this pass found, same family as `.match?`/
|
|
@@ -792,48 +1002,46 @@ module Hecks
|
|
|
792
1002
|
# "literal"` shape in plan.bluebook, service_task.bluebook,
|
|
793
1003
|
# route.bluebook, and subscription.bluebook, all equally silent
|
|
794
1004
|
# until a real dispatch (never validate) exercised the
|
|
795
|
-
# predicate.
|
|
796
|
-
#
|
|
797
|
-
#
|
|
798
|
-
#
|
|
799
|
-
#
|
|
800
|
-
#
|
|
801
|
-
#
|
|
802
|
-
#
|
|
803
|
-
#
|
|
804
|
-
#
|
|
805
|
-
#
|
|
806
|
-
#
|
|
807
|
-
#
|
|
808
|
-
#
|
|
809
|
-
#
|
|
810
|
-
#
|
|
811
|
-
#
|
|
812
|
-
#
|
|
813
|
-
#
|
|
814
|
-
#
|
|
815
|
-
#
|
|
816
|
-
#
|
|
817
|
-
#
|
|
818
|
-
#
|
|
819
|
-
#
|
|
820
|
-
#
|
|
821
|
-
#
|
|
822
|
-
#
|
|
823
|
-
#
|
|
824
|
-
#
|
|
825
|
-
#
|
|
826
|
-
#
|
|
827
|
-
#
|
|
828
|
-
#
|
|
829
|
-
#
|
|
830
|
-
#
|
|
831
|
-
#
|
|
832
|
-
#
|
|
833
|
-
#
|
|
834
|
-
#
|
|
835
|
-
# Rust side — change them in lockstep or rust_conformance
|
|
836
|
-
# diverges.
|
|
1005
|
+
# predicate.
|
|
1006
|
+
#
|
|
1007
|
+
# Unwraps both the bare (undotted) case and a dotted lookup's own
|
|
1008
|
+
# terminal value: a dotted lookup that navigates through an
|
|
1009
|
+
# entity/list element to a nested field (`leg.voyage`, where
|
|
1010
|
+
# `voyage` is itself a single-field VO) lands on the same
|
|
1011
|
+
# unwrapped-`Value` shape a bare lookup does, and hits the
|
|
1012
|
+
# identical silent `Value#==` failure without the same
|
|
1013
|
+
# "this VO IS its scalar" treatment — only the intermediate hops
|
|
1014
|
+
# need raw `#[]` addressing to keep navigating. `unwrap_scalar` is
|
|
1015
|
+
# idempotent on an already-raw scalar (a String/Integer doesn't
|
|
1016
|
+
# respond to `#to_h`), so this is safe for `field.value`-shaped
|
|
1017
|
+
# dotted lookups too — they already return a raw scalar and stay
|
|
1018
|
+
# unaffected.
|
|
1019
|
+
#
|
|
1020
|
+
# Gated on the sole attribute's *count*, never its *name*: gating
|
|
1021
|
+
# on a literal `:value` key would be a lie of omission for
|
|
1022
|
+
# `Money{amount}` and every other single-field value object whose
|
|
1023
|
+
# author picked a domain name for the field — the same
|
|
1024
|
+
# "this VO IS its scalar" reading
|
|
1025
|
+
# ([[feedback_name_the_scalar_field]],
|
|
1026
|
+
# `Behaviour::ValueObject#sole_attribute`) applies regardless of
|
|
1027
|
+
# what the sole field happens to be called, and a name gate would
|
|
1028
|
+
# make a bare `balance > 0` work for a `Balance{value}` while
|
|
1029
|
+
# silently comparing a whole VO for a `Balance{amount}`. A
|
|
1030
|
+
# declared `Runtime::Value` reads its own `sole_attribute` (the
|
|
1031
|
+
# declaration's answer, not the stored hash's); any other
|
|
1032
|
+
# to_h-able (a Struct, a bespoke wrapper with no declaration to
|
|
1033
|
+
# consult) keeps the `{value: X}`-only unwrap, so nothing that
|
|
1034
|
+
# never was a value object gains a surprise unwrapping.
|
|
1035
|
+
# `rust/src/kernel/json.rs`'s `impl Fielded for Json` mirrors the
|
|
1036
|
+
# count-only reading on the Rust side — change them in lockstep
|
|
1037
|
+
# or rust_conformance diverges.
|
|
1038
|
+
#
|
|
1039
|
+
# @param value [Object] a resolved lookup or path-walk result
|
|
1040
|
+
# @return [Object] `value` itself if it is a Hash, Array, or has no
|
|
1041
|
+
# `#to_h`; a declared `Runtime::Value`'s sole attribute's own
|
|
1042
|
+
# value, read via `sole.name`, if it has exactly one attribute;
|
|
1043
|
+
# `value[:value]` for any other to_h-able with exactly one key
|
|
1044
|
+
# named `:value`; `value` unchanged otherwise
|
|
837
1045
|
def unwrap_scalar(value)
|
|
838
1046
|
return value unless value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
|
|
839
1047
|
|
|
@@ -846,6 +1054,17 @@ module Hecks
|
|
|
846
1054
|
hash.size == 1 && hash.key?(:value) ? hash[:value] : value
|
|
847
1055
|
end
|
|
848
1056
|
|
|
1057
|
+
# Resolves a single unqualified name — `attrs` first, `state`
|
|
1058
|
+
# second.
|
|
1059
|
+
#
|
|
1060
|
+
# @param name [String, Symbol] the attribute or argument name
|
|
1061
|
+
# @param state [Hash{Symbol => Object}, #[]] the stored attribute
|
|
1062
|
+
# values
|
|
1063
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
1064
|
+
# values, checked first
|
|
1065
|
+
# @return [Object] the value held under `name`
|
|
1066
|
+
# @raise [EvaluationError] if `name` is in neither `attrs` nor
|
|
1067
|
+
# `state`
|
|
849
1068
|
def fetch(name, state, attrs)
|
|
850
1069
|
key = name.to_sym
|
|
851
1070
|
return attrs[key] if attrs.key?(key)
|
|
@@ -854,21 +1073,45 @@ module Hecks
|
|
|
854
1073
|
raise EvaluationError, "cannot resolve #{name.inspect} — no such attribute or argument"
|
|
855
1074
|
end
|
|
856
1075
|
|
|
1076
|
+
# Reports whether `state` holds `key`, duck-typed for a `state`
|
|
1077
|
+
# that does not respond to `:key?`.
|
|
1078
|
+
#
|
|
1079
|
+
# @param state [Hash{Symbol => Object}, #[]] the stored attribute
|
|
1080
|
+
# values
|
|
1081
|
+
# @param key [Symbol] the attribute name to look for
|
|
1082
|
+
# @return [Boolean] `state.key?(key)` when `state` responds to
|
|
1083
|
+
# `:key?`; otherwise whether `state[key]` is non-nil
|
|
857
1084
|
def known?(state, key)
|
|
858
1085
|
return state.key?(key) if state.respond_to?(:key?)
|
|
859
1086
|
|
|
860
1087
|
!state[key].nil?
|
|
861
1088
|
end
|
|
862
1089
|
|
|
1090
|
+
# Returns `value` if it is already numeric, or `nil` if it is not.
|
|
1091
|
+
#
|
|
1092
|
+
# @param value [Object] the value to check
|
|
1093
|
+
# @return [Integer, Float, nil] `value` unchanged if it is an
|
|
1094
|
+
# Integer or Float, otherwise `nil`
|
|
863
1095
|
def numeric(value)
|
|
864
1096
|
value if value.is_a?(Integer) || value.is_a?(Float)
|
|
865
1097
|
end
|
|
866
1098
|
|
|
1099
|
+
# Coerces `value` to a number or refuses it.
|
|
1100
|
+
#
|
|
1101
|
+
# @param value [Object] the value to check
|
|
1102
|
+
# @param operation [String] the operation name, for the refusal
|
|
1103
|
+
# message
|
|
1104
|
+
# @return [Integer, Float] `value` unchanged
|
|
1105
|
+
# @raise [EvaluationError] if `value` is not an Integer or Float
|
|
867
1106
|
def require_number(value, operation)
|
|
868
1107
|
numeric(value) ||
|
|
869
1108
|
raise(EvaluationError, "#{operation} expects a number, got #{describe(value)}")
|
|
870
1109
|
end
|
|
871
1110
|
|
|
1111
|
+
# Renders `value` the way it should read inside a refusal message.
|
|
1112
|
+
#
|
|
1113
|
+
# @param value [Object] the value to render
|
|
1114
|
+
# @return [String] `Rendering.describe(value)`
|
|
872
1115
|
def describe(value) = Rendering.describe(value)
|
|
873
1116
|
end
|
|
874
1117
|
end
|