hecks 1.4.0 → 1.5.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/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.hecksagon +19 -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/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 +58 -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/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/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 +29 -5
- data/lib/hecks/projector.rb +74 -6
- 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 +11 -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
|
@@ -16,6 +16,10 @@ module Hecks
|
|
|
16
16
|
include RuleReference
|
|
17
17
|
include WordGate
|
|
18
18
|
|
|
19
|
+
# @param name [String] the value object's name, as written after `value_object`
|
|
20
|
+
# @param owner_value_objects [Array<Bluebook::ValueObject>] the owning aggregate's other
|
|
21
|
+
# value objects, built so far, that this one's `invariant` references may resolve
|
|
22
|
+
# against
|
|
19
23
|
def initialize(name, owner_value_objects: [])
|
|
20
24
|
@name = name
|
|
21
25
|
@invariants = []
|
|
@@ -46,7 +50,11 @@ module Hecks
|
|
|
46
50
|
# still needs to read it. `block_given?` is what tells the two
|
|
47
51
|
# calling shapes apart: the type-position form
|
|
48
52
|
# (`one_of("a", "b")`) never passes a block, only the wrapper does.
|
|
49
|
-
#
|
|
53
|
+
# Declares this value object as a closed set, either from bare values or (under
|
|
54
|
+
# shadow-parsing) a legacy `do ... end` wrapper of `member` lines.
|
|
55
|
+
#
|
|
56
|
+
# Answers the `one_of` word through the table's `calls:` column —
|
|
57
|
+
# item #13's full metaprogrammed dispatch
|
|
50
58
|
# (slice 5). Same name as `AttributeCollector#one_of_impl`'s own
|
|
51
59
|
# — required for the `super(*values)` call below to keep
|
|
52
60
|
# resolving; see that method's own comment. Reached directly
|
|
@@ -54,6 +62,13 @@ module Hecks
|
|
|
54
62
|
# block-wrapper form has its own row, distinct from "Type"'s),
|
|
55
63
|
# not through the Type-position fallback this word's other
|
|
56
64
|
# context uses.
|
|
65
|
+
#
|
|
66
|
+
# @param values [Array<String, Symbol>] permitted values, given bare (no block); at
|
|
67
|
+
# least one is required
|
|
68
|
+
# @yield a legacy `member` wrapper body; only read under shadow-parsing
|
|
69
|
+
# @return [void]
|
|
70
|
+
# @raise [Bluebook::DSL::Malformed] if given no values and no block, or a block outside
|
|
71
|
+
# shadow-parsing
|
|
57
72
|
def one_of_impl(*values, &block)
|
|
58
73
|
unless block
|
|
59
74
|
# No values, no block is the scalar spelling — nonsensical, not
|
|
@@ -83,9 +98,16 @@ module Hecks
|
|
|
83
98
|
instance_eval(&block)
|
|
84
99
|
end
|
|
85
100
|
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
101
|
+
# Declares one member row of a closed set, one value per attribute.
|
|
102
|
+
#
|
|
103
|
+
# Answers the `member` word through the table's `calls:` column —
|
|
104
|
+
# item #13's full metaprogrammed dispatch (slice 4c). Bootstrap-reachable, in
|
|
105
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
106
|
+
#
|
|
107
|
+
# @param fields [Hash{Symbol => Object}] each declared attribute's name mapped to this
|
|
108
|
+
# member's value for it
|
|
109
|
+
# @return [Array<Hash{Symbol => Object}>] every member declared so far, this one last
|
|
110
|
+
# @raise [Bluebook::DSL::Malformed] if `fields` is empty
|
|
89
111
|
def member_impl(**fields)
|
|
90
112
|
raise Malformed, "#{@name} declared an empty member" if fields.empty?
|
|
91
113
|
|
|
@@ -105,9 +127,20 @@ module Hecks
|
|
|
105
127
|
# the referencing value object's own build time, against
|
|
106
128
|
# whatever sibling value objects the aggregate has already
|
|
107
129
|
# built, the same ordering rule `given` carries.
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
130
|
+
# Declares a rule this value object's own value must satisfy, or references one a sibling
|
|
131
|
+
# value object on the same aggregate already declared.
|
|
132
|
+
#
|
|
133
|
+
# Answers the `invariant` word through the table's `calls:` column —
|
|
134
|
+
# item #13's full metaprogrammed dispatch (slice 4b). Bootstrap-reachable, in
|
|
135
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
136
|
+
#
|
|
137
|
+
# @param description [String] the rule's description; also the name a sibling value
|
|
138
|
+
# object references it by when no block is given
|
|
139
|
+
# @yield the predicate body; evaluated for its extracted source, never called directly
|
|
140
|
+
# @return [void]
|
|
141
|
+
# @raise [Bluebook::DSL::Malformed] if given a block whose source cannot be extracted or
|
|
142
|
+
# uses a pattern construct engines disagree on, or given no block and no sibling value
|
|
143
|
+
# object on this aggregate declares a rule with this description
|
|
111
144
|
def invariant_impl(description, &predicate)
|
|
112
145
|
return reference_named_invariant(description) unless predicate
|
|
113
146
|
|
|
@@ -134,6 +167,12 @@ module Hecks
|
|
|
134
167
|
|
|
135
168
|
public
|
|
136
169
|
|
|
170
|
+
# Assembles the declared attributes, invariants and members into a `ValueObject`.
|
|
171
|
+
#
|
|
172
|
+
# @return [Bluebook::ValueObject] the built value object; `closed_set` is true when any
|
|
173
|
+
# member was declared
|
|
174
|
+
# @raise [Bluebook::DSL::Malformed] if `one_of:` was declared on an attribute alongside
|
|
175
|
+
# other attributes on the same value object
|
|
137
176
|
def build
|
|
138
177
|
if @inline_closed_set_field && attributes.size > 1
|
|
139
178
|
raise Malformed,
|
|
@@ -149,6 +188,15 @@ module Hecks
|
|
|
149
188
|
)
|
|
150
189
|
end
|
|
151
190
|
|
|
191
|
+
# Evaluates a `value_object` block against a fresh builder and returns what it built.
|
|
192
|
+
#
|
|
193
|
+
# @param name [String] the value object's name
|
|
194
|
+
# @param owner_value_objects [Array<Bluebook::ValueObject>] the owning aggregate's other
|
|
195
|
+
# value objects, for `invariant` references to resolve against
|
|
196
|
+
# @yield the value object body, evaluated with the builder as `self`; may be omitted
|
|
197
|
+
# @return [Bluebook::ValueObject] the built value object
|
|
198
|
+
# @raise [Bluebook::DSL::Malformed] if the body declares a rule extraction cannot read,
|
|
199
|
+
# `one_of:` on more than one attribute, or uses a word the grammar does not admit
|
|
152
200
|
def self.build(name, owner_value_objects: [], &block)
|
|
153
201
|
builder = new(name, owner_value_objects: owner_value_objects)
|
|
154
202
|
builder.instance_eval(&block) if block
|
|
@@ -70,6 +70,24 @@ module Hecks
|
|
|
70
70
|
attr_reader :description, :identified_by, :identity_paths, :identity_heads,
|
|
71
71
|
:attributes, :commands, :queries, :entities, :preconditions, :invariants, :lifecycle
|
|
72
72
|
|
|
73
|
+
# Mints a new piece class for one declared entity and absorbs its fields into it.
|
|
74
|
+
#
|
|
75
|
+
# @param name [String, Symbol] the entity's declared name
|
|
76
|
+
# @param description [String, nil] the entity's declared prose description
|
|
77
|
+
# @param identified_by [String, Symbol, Array<String, Symbol>, nil] the identity
|
|
78
|
+
# path(s) this entity is addressed by
|
|
79
|
+
# @param attributes [Array<Bluebook::Attribute>] the entity's declared fields
|
|
80
|
+
# @param commands [Array<Class>] the command classes (`Bluebook::Command` subclasses)
|
|
81
|
+
# declared on this entity
|
|
82
|
+
# @param queries [Array<Bluebook::Query>] the queries declared on this entity
|
|
83
|
+
# @param entities [Array<Class>] the entity classes (`Bluebook::Entity` subclasses)
|
|
84
|
+
# nested directly under this entity
|
|
85
|
+
# @param preconditions [Array<Bluebook::Given>] this entity's own named `given`s
|
|
86
|
+
# @param invariants [Array<Bluebook::Invariant>] the rules checked against every
|
|
87
|
+
# instance of this entity
|
|
88
|
+
# @param lifecycle [Bluebook::Lifecycle, nil] the entity's declared state machine,
|
|
89
|
+
# or `nil` if it declares none
|
|
90
|
+
# @return [Class] the minted piece class (a `Bluebook::Entity` subclass)
|
|
73
91
|
def declare(name:, description: nil, identified_by: nil, attributes: [],
|
|
74
92
|
commands: [], queries: [], entities: [], preconditions: [], invariants: [], lifecycle: nil)
|
|
75
93
|
piece = Class.new(self)
|
|
@@ -85,6 +103,17 @@ module Hecks
|
|
|
85
103
|
# Assigns what the language declares, then hands off to the
|
|
86
104
|
# behaviour's own `settle` — derived identity and the name
|
|
87
105
|
# indexes, neither of which the declaration states.
|
|
106
|
+
#
|
|
107
|
+
# @param description [String, nil] see `declare`
|
|
108
|
+
# @param identified_by [String, Symbol, Array<String, Symbol>, nil] see `declare`
|
|
109
|
+
# @param attributes [Array<Bluebook::Attribute>] see `declare`
|
|
110
|
+
# @param commands [Array<Class>] see `declare`
|
|
111
|
+
# @param queries [Array<Bluebook::Query>] see `declare`
|
|
112
|
+
# @param entities [Array<Class>] see `declare`
|
|
113
|
+
# @param preconditions [Array<Bluebook::Given>] see `declare`
|
|
114
|
+
# @param invariants [Array<Bluebook::Invariant>] see `declare`
|
|
115
|
+
# @param lifecycle [Bluebook::Lifecycle, nil] see `declare`
|
|
116
|
+
# @return [Class] this piece's own class, self, once identity and indexes are derived
|
|
88
117
|
def absorb(description:, identified_by:, attributes:, commands:, queries:, entities:, preconditions:, invariants:,
|
|
89
118
|
lifecycle:)
|
|
90
119
|
@description = description
|
|
@@ -4,11 +4,13 @@ require_relative "resolver"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Bluebook
|
|
6
6
|
module Expression
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
7
|
+
# Walks the real Evaluator/Resolver AST (the same objects a live
|
|
8
|
+
# dispatch parses `given`/`ensures`/invariant text into — see
|
|
9
|
+
# docs/implemented/guides/running-a-runtime.md's "The expression
|
|
10
|
+
# grammar") and emits plain, JSON-serializable Ruby Hashes, tagged
|
|
11
|
+
# by `"op"`.
|
|
12
|
+
#
|
|
13
|
+
# ## Why this exists beside `rust/project/expr_emitter.rb`
|
|
12
14
|
#
|
|
13
15
|
# The same two-method walk `rust/project/expr_emitter.rb`'s own
|
|
14
16
|
# `emit_bool`/`emit_resolver` already do, over the same AST — that
|
|
@@ -26,8 +28,9 @@ module Hecks
|
|
|
26
28
|
# exact same relationship `rust/project`'s own `Expr` literals
|
|
27
29
|
# already have to the compiled kernel, one layer further out.
|
|
28
30
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
+
# ## Why it lives in core `lib/hecks`, not `rust/project/`
|
|
32
|
+
#
|
|
33
|
+
# `rust/project.rb` is a separate, downstream toolchain
|
|
31
34
|
# (`lib/hecks/projector.rb`'s own header: "a whole separate Ruby
|
|
32
35
|
# program"), never `require`d by core `lib/hecks/bluebook/*.rb`
|
|
33
36
|
# files (confirmed: no core file does). `value_object.rb`'s own
|
|
@@ -37,22 +40,24 @@ module Hecks
|
|
|
37
40
|
# run — so it belongs beside `Evaluator`/`Resolver` themselves, not
|
|
38
41
|
# bolted onto a tool that only sometimes runs.
|
|
39
42
|
#
|
|
40
|
-
# Complete, not corpus-scoped
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# `
|
|
47
|
-
#
|
|
48
|
-
# `
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
43
|
+
# ## Complete, not corpus-scoped
|
|
44
|
+
#
|
|
45
|
+
# Every node this grammar admits gets a real arm, the identical
|
|
46
|
+
# "raise, don't silently drop" discipline `expr_emitter.rb`'s own
|
|
47
|
+
# `emit_bool`/`emit_resolver` already hold to — even though, as of
|
|
48
|
+
# this writing, no real corpus value object invariant exercises
|
|
49
|
+
# `Include`/`Modulo`/`BlockPredicate`/`Find`/`Array`/`MatchesRegex`/
|
|
50
|
+
# `Presence`/`Assignment`/`Split`/`StartsWith`/`EndsWith`/`First`/
|
|
51
|
+
# `Last` (only `given`/`ensures` clauses do, elsewhere in the corpus
|
|
52
|
+
# — a different consumer of this same grammar). `rust/host/src/
|
|
53
|
+
# expr_json.rs`'s own header names exactly which of these its
|
|
54
|
+
# interpreter evaluates for real today versus refuses cleanly — a
|
|
55
|
+
# narrower, deliberate, documented boundary on the interpreting
|
|
56
|
+
# side, not on this emitting side: an author is free to write any
|
|
57
|
+
# real expression in a value object's own `invariant`, and this
|
|
58
|
+
# always emits it faithfully; whether `rust/host` can yet check it
|
|
59
|
+
# at mint time is that file's own question to answer, not this
|
|
60
|
+
# one's to pre-empt by refusing to even try.
|
|
56
61
|
module AstJson
|
|
57
62
|
module_function
|
|
58
63
|
|
|
@@ -74,10 +79,20 @@ module Hecks
|
|
|
74
79
|
# function of `canonical`: the IR carries both so a reader that
|
|
75
80
|
# only displays keeps the text, and a reader that evaluates never
|
|
76
81
|
# re-parses it.
|
|
82
|
+
#
|
|
83
|
+
# @param rule [Bluebook::Given, Bluebook::Invariant] the rule to
|
|
84
|
+
# render; `rule.ast` is used when already computed, otherwise
|
|
85
|
+
# `rule.canonical` is parsed and emitted fresh
|
|
86
|
+
# @return [Hash{Symbol => Object}] `:description` (`String`),
|
|
87
|
+
# `:canonical` (`String`), and `:ast` (`Hash`, the JSON AST)
|
|
77
88
|
def rule_row(rule)
|
|
78
89
|
{ description: rule.description, canonical: rule.canonical, ast: rule.ast || emit_predicate(rule.canonical) }
|
|
79
90
|
end
|
|
80
91
|
|
|
92
|
+
# Parses `canonical` and emits its JSON AST in one step.
|
|
93
|
+
#
|
|
94
|
+
# @param canonical [String] a canonical predicate source string
|
|
95
|
+
# @return [Hash] the JSON-shaped boolean AST, tagged by `"op"`
|
|
81
96
|
def emit_predicate(canonical)
|
|
82
97
|
emit_bool(Evaluator.parse(canonical))
|
|
83
98
|
end
|
|
@@ -89,6 +104,17 @@ module Hecks
|
|
|
89
104
|
# engine reading it is a defect in the bluebook, refused at build.
|
|
90
105
|
# Walks the emitted AST, so every rule site (givens, ensures,
|
|
91
106
|
# invariants, preconditions, a policy's where) gets the one check.
|
|
107
|
+
#
|
|
108
|
+
# @param ast [Hash] a JSON AST node, as emitted by `emit_predicate`
|
|
109
|
+
# or `emit_bool`
|
|
110
|
+
# @param owner [String] the declaring bluebook's name, for the
|
|
111
|
+
# refusal message
|
|
112
|
+
# @param word [String] the rule's own kind and description
|
|
113
|
+
# (such as `"given \"total is positive\""`), for the refusal
|
|
114
|
+
# message
|
|
115
|
+
# @return [Hash] `ast`, unchanged
|
|
116
|
+
# @raise [Bluebook::DSL::Malformed] if any `matches_regex` node's
|
|
117
|
+
# pattern uses a construct `PatternSubset` does not admit
|
|
92
118
|
def refuse_unshared_patterns!(ast, owner:, word:)
|
|
93
119
|
return ast if Hecks::Bluebook::MetaValidator.shadow_parsing? # frozen era text is history
|
|
94
120
|
|
|
@@ -107,6 +133,11 @@ module Hecks
|
|
|
107
133
|
|
|
108
134
|
# Every name a rule resolves at its root — the first segment of
|
|
109
135
|
# each `lookup` path, unique, in first-seen order.
|
|
136
|
+
#
|
|
137
|
+
# @param ast [Hash] a JSON AST node, as emitted by `emit_predicate`
|
|
138
|
+
# or `emit_bool`
|
|
139
|
+
# @return [Array<String>] each `lookup` node's root name, unique,
|
|
140
|
+
# in first-seen order
|
|
110
141
|
def lookup_heads(ast)
|
|
111
142
|
heads = []
|
|
112
143
|
each_node(ast) do |node|
|
|
@@ -115,6 +146,13 @@ module Hecks
|
|
|
115
146
|
heads.uniq
|
|
116
147
|
end
|
|
117
148
|
|
|
149
|
+
# Visits `node` and every Hash node nested inside it, depth-first.
|
|
150
|
+
#
|
|
151
|
+
# @param node [Hash, Array, Object] a JSON AST node, or an Array or
|
|
152
|
+
# scalar value found while walking one; a scalar is a silent no-op
|
|
153
|
+
# @yieldparam node [Hash] each Hash node reached, including `node`
|
|
154
|
+
# itself
|
|
155
|
+
# @return [void]
|
|
118
156
|
def each_node(node, &block)
|
|
119
157
|
case node
|
|
120
158
|
when ::Hash
|
|
@@ -125,6 +163,17 @@ module Hecks
|
|
|
125
163
|
end
|
|
126
164
|
end
|
|
127
165
|
|
|
166
|
+
# Emits the JSON form of one boolean-position Evaluator node.
|
|
167
|
+
#
|
|
168
|
+
# @param node [Bluebook::Expression::Evaluator::Or,
|
|
169
|
+
# Bluebook::Expression::Evaluator::And,
|
|
170
|
+
# Bluebook::Expression::Evaluator::Not,
|
|
171
|
+
# Bluebook::Expression::Evaluator::Compare,
|
|
172
|
+
# Bluebook::Expression::Evaluator::Include,
|
|
173
|
+
# Bluebook::Expression::Evaluator::Resolve] the boolean-position
|
|
174
|
+
# AST node to emit
|
|
175
|
+
# @return [Hash] the JSON AST for `node`, tagged by `"op"`
|
|
176
|
+
# @raise [RuntimeError] if `node` is not one of the handled classes
|
|
128
177
|
def emit_bool(node)
|
|
129
178
|
case node
|
|
130
179
|
when Evaluator::Or then { "op" => "or", "left" => emit_bool(node.left), "right" => emit_bool(node.right) }
|
|
@@ -143,6 +192,12 @@ module Hecks
|
|
|
143
192
|
end
|
|
144
193
|
end
|
|
145
194
|
|
|
195
|
+
# Emits the JSON form of one comparison operator.
|
|
196
|
+
#
|
|
197
|
+
# @param comparator [Bluebook::Expression::Evaluator::Operator] the
|
|
198
|
+
# operator to emit
|
|
199
|
+
# @return [Hash{String => Boolean}] `"less_than"`, `"equal"`, and
|
|
200
|
+
# `"negated"`, exactly as `comparator` carries them
|
|
146
201
|
def emit_comparison(comparator)
|
|
147
202
|
{ "less_than" => comparator.compares_less_than, "equal" => comparator.compares_equal, "negated" => comparator.negated }
|
|
148
203
|
end
|
|
@@ -158,6 +213,16 @@ module Hecks
|
|
|
158
213
|
EQ = Evaluator::OPERATORS.find { |op| op.symbol == "==" }
|
|
159
214
|
private_constant :EQ
|
|
160
215
|
|
|
216
|
+
# Emits the JSON form of one `include` node.
|
|
217
|
+
#
|
|
218
|
+
# @param node [Bluebook::Expression::Evaluator::Include] the
|
|
219
|
+
# `include` node to emit
|
|
220
|
+
# @return [Hash] the JSON AST: `{"op" => "include", ...}` for a
|
|
221
|
+
# non-literal haystack, `{"op" => "bool", "value" => false}` for
|
|
222
|
+
# an empty literal array haystack, or an `"or"`-of-`"compare"`
|
|
223
|
+
# tree of equalities for a non-empty literal array haystack
|
|
224
|
+
# @raise [RuntimeError] if `node.haystack`, `node.needle`, or one of
|
|
225
|
+
# the haystack's literal elements is an unhandled resolver node
|
|
161
226
|
def emit_include(node)
|
|
162
227
|
return { "op" => "include", "haystack" => emit_resolver(node.haystack), "needle" => emit_resolver(node.needle) } \
|
|
163
228
|
unless node.haystack.is_a?(Resolver::ArrayLiteral)
|
|
@@ -176,6 +241,33 @@ module Hecks
|
|
|
176
241
|
# ("every node this grammar admits gets a real arm"); splitting it
|
|
177
242
|
# into several methods would hide whether the set is still
|
|
178
243
|
# exhaustive instead of making that visible at a glance.
|
|
244
|
+
#
|
|
245
|
+
# @param node [Bluebook::Expression::Resolver::IntegerLiteral,
|
|
246
|
+
# Bluebook::Expression::Resolver::FloatLiteral,
|
|
247
|
+
# Bluebook::Expression::Resolver::StringLiteral,
|
|
248
|
+
# Bluebook::Expression::Resolver::BoolLiteral,
|
|
249
|
+
# Bluebook::Expression::Resolver::NilLiteral,
|
|
250
|
+
# Bluebook::Expression::Resolver::Lookup,
|
|
251
|
+
# Bluebook::Expression::Resolver::Addition,
|
|
252
|
+
# Bluebook::Expression::Resolver::SignTest,
|
|
253
|
+
# Bluebook::Expression::Resolver::Empty,
|
|
254
|
+
# Bluebook::Expression::Resolver::ToS,
|
|
255
|
+
# Bluebook::Expression::Resolver::Modulo,
|
|
256
|
+
# Bluebook::Expression::Resolver::Size,
|
|
257
|
+
# Bluebook::Expression::Resolver::BlockPredicate,
|
|
258
|
+
# Bluebook::Expression::Resolver::Find,
|
|
259
|
+
# Bluebook::Expression::Resolver::ArrayLiteral,
|
|
260
|
+
# Bluebook::Expression::Resolver::MatchesRegex,
|
|
261
|
+
# Bluebook::Expression::Resolver::Presence,
|
|
262
|
+
# Bluebook::Expression::Resolver::Assignment,
|
|
263
|
+
# Bluebook::Expression::Resolver::Split,
|
|
264
|
+
# Bluebook::Expression::Resolver::StartsWith,
|
|
265
|
+
# Bluebook::Expression::Resolver::EndsWith,
|
|
266
|
+
# Bluebook::Expression::Resolver::First,
|
|
267
|
+
# Bluebook::Expression::Resolver::Last] the value-position AST
|
|
268
|
+
# node to emit
|
|
269
|
+
# @return [Hash] the JSON AST for `node`, tagged by `"op"`
|
|
270
|
+
# @raise [RuntimeError] if `node` is not one of the handled classes
|
|
179
271
|
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
180
272
|
def emit_resolver(node)
|
|
181
273
|
case node
|
|
@@ -26,8 +26,19 @@ module Hecks
|
|
|
26
26
|
module AstReader
|
|
27
27
|
module_function
|
|
28
28
|
|
|
29
|
+
# Reads a whole predicate's own `"op"`-tagged JSON back into its AST.
|
|
30
|
+
#
|
|
31
|
+
# @param json [Hash{String => Object}] the `"op"`-tagged JSON a rule row's own
|
|
32
|
+
# `ast` carries
|
|
33
|
+
# @return [Evaluator::Or, Evaluator::And, Evaluator::Not, Evaluator::Compare,
|
|
34
|
+
# Evaluator::Include, Evaluator::Resolve] the boolean/comparison AST node
|
|
29
35
|
def read_predicate(json) = read_bool(json)
|
|
30
36
|
|
|
37
|
+
# Reads one boolean/comparison node, recursing into its own children.
|
|
38
|
+
#
|
|
39
|
+
# @param json [Hash{String => Object}] the `"op"`-tagged JSON for one node
|
|
40
|
+
# @return [Evaluator::Or, Evaluator::And, Evaluator::Not, Evaluator::Compare,
|
|
41
|
+
# Evaluator::Include, Evaluator::Resolve] the boolean/comparison AST node
|
|
31
42
|
def read_bool(json)
|
|
32
43
|
case json.fetch("op")
|
|
33
44
|
when "or" then Evaluator::Or.new(left: read_bool(json["left"]), right: read_bool(json["right"]))
|
|
@@ -46,6 +57,10 @@ module Hecks
|
|
|
46
57
|
# `Operator` carrying that exact triple is the one `parse` would
|
|
47
58
|
# have chosen, because the roster (`expression/projection.json`)
|
|
48
59
|
# holds one symbol per triple.
|
|
60
|
+
#
|
|
61
|
+
# @param cmp [Hash{String => Boolean}] `{"less_than"=>, "equal"=>, "negated"=>}`
|
|
62
|
+
# @return [Evaluator::Operator] the operator carrying that exact triple
|
|
63
|
+
# @raise [RuntimeError] if no operator in `Evaluator::OPERATORS` has that triple
|
|
49
64
|
def operator(cmp)
|
|
50
65
|
Evaluator::OPERATORS.find do |op|
|
|
51
66
|
op.compares_less_than == cmp.fetch("less_than") &&
|
|
@@ -54,6 +69,15 @@ module Hecks
|
|
|
54
69
|
end or raise "no comparison operator has the triple #{cmp.inspect}"
|
|
55
70
|
end
|
|
56
71
|
|
|
72
|
+
# Reads one dotted/arithmetic leaf node, recursing into its own children.
|
|
73
|
+
#
|
|
74
|
+
# @param json [Hash{String => Object}] the `"op"`-tagged JSON for one leaf node
|
|
75
|
+
# @return [Object] a `Resolver` AST node — one of `IntegerLiteral`, `FloatLiteral`,
|
|
76
|
+
# `StringLiteral`, `BoolLiteral`, `NilLiteral`, `ArrayLiteral`, `Lookup`,
|
|
77
|
+
# `Addition`, `SignTest`, `Empty`, `ToS`, `Modulo`, `Size`, `First`, `Last`,
|
|
78
|
+
# `BlockPredicate`, `Find`, `MatchesRegex`, `Presence`, `Assignment`, `Split`,
|
|
79
|
+
# `StartsWith`, or `EndsWith`
|
|
80
|
+
# @raise [RuntimeError] if `json["op"]` names no known resolver op
|
|
57
81
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity -- one arm
|
|
58
82
|
# per AstJson op is the point; splitting the case would hide the roster.
|
|
59
83
|
def read_resolver(json)
|
|
@@ -98,6 +122,11 @@ module Hecks
|
|
|
98
122
|
# triple is what evaluates. Recover the spelling from the
|
|
99
123
|
# vocabulary so a rebuilt node refuses with the same message the
|
|
100
124
|
# parsed one would.
|
|
125
|
+
#
|
|
126
|
+
# @param operator [Evaluator::Operator] the comparison operator a sign test's own
|
|
127
|
+
# triple resolved to
|
|
128
|
+
# @return [String] the sign test's own vocabulary name, or `operator.symbol` when
|
|
129
|
+
# no sign test shares its triple
|
|
101
130
|
def sign_test_name(operator)
|
|
102
131
|
Resolver::SIGN_TEST_OPERATORS.key(operator.symbol) || operator.symbol
|
|
103
132
|
end
|
|
@@ -26,6 +26,13 @@ module Hecks
|
|
|
26
26
|
|
|
27
27
|
module_function
|
|
28
28
|
|
|
29
|
+
# Returns the admitted normalisation rules as plain Hashes, for
|
|
30
|
+
# embedding in a bluebook's read-back contract.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Hash{Symbol => String}>] one hash per rule, in
|
|
33
|
+
# declared `position` order, with keys `:strategy`,
|
|
34
|
+
# `:source_token`, `:replacement`, `:boundary`, and `:position`
|
|
35
|
+
# (stringified)
|
|
29
36
|
def table
|
|
30
37
|
RULES.sort_by(&:position).map do |rule|
|
|
31
38
|
{
|
|
@@ -38,10 +45,25 @@ module Hecks
|
|
|
38
45
|
end
|
|
39
46
|
end
|
|
40
47
|
|
|
48
|
+
# Rewrites `source` into its canonical spelling by applying every
|
|
49
|
+
# admitted `RULES` entry, in declared `position` order.
|
|
50
|
+
#
|
|
51
|
+
# @param source [String] the raw predicate source to normalise
|
|
52
|
+
# @return [String] the canonicalised text, stripped of leading and
|
|
53
|
+
# trailing whitespace
|
|
41
54
|
def apply(source)
|
|
42
55
|
RULES.sort_by(&:position).reduce(source.to_s) { |text, rule| step(text, rule) }.strip
|
|
43
56
|
end
|
|
44
57
|
|
|
58
|
+
# Applies one normalisation `rule` to `text`, dispatching on the
|
|
59
|
+
# rule's strategy.
|
|
60
|
+
#
|
|
61
|
+
# @param text [String] the source text (or string-literal segment) to
|
|
62
|
+
# transform
|
|
63
|
+
# @param rule [Rule] the normalisation rule to apply
|
|
64
|
+
# @return [String] `text` after the rule is applied
|
|
65
|
+
# @raise [ArgumentError] if `rule.strategy` names a strategy this
|
|
66
|
+
# method does not implement
|
|
45
67
|
def step(text, rule)
|
|
46
68
|
case rule.strategy
|
|
47
69
|
when "collapse_whitespace" then map_outside_strings(text) { |segment| segment.gsub(/\s+/, " ") }
|
|
@@ -51,6 +73,14 @@ module Hecks
|
|
|
51
73
|
end
|
|
52
74
|
end
|
|
53
75
|
|
|
76
|
+
# Substitutes every occurrence of `rule.source_token` in `text` with
|
|
77
|
+
# `rule.replacement`, honoring the rule's word-boundary setting.
|
|
78
|
+
#
|
|
79
|
+
# @param text [String] the source text (or string-literal segment) to
|
|
80
|
+
# transform
|
|
81
|
+
# @param rule [Rule] the `"replace"`-strategy rule supplying the
|
|
82
|
+
# token, replacement, and boundary
|
|
83
|
+
# @return [String] `text` with the substitution applied
|
|
54
84
|
def replace(text, rule)
|
|
55
85
|
map_outside_strings(text) do |segment|
|
|
56
86
|
if rule.boundary == "none"
|
|
@@ -61,21 +91,30 @@ module Hecks
|
|
|
61
91
|
end
|
|
62
92
|
end
|
|
63
93
|
|
|
64
|
-
# Applies
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# `"a.length"`
|
|
69
|
-
# source outside the quotes
|
|
70
|
-
# compares a string attribute against, not merely how the
|
|
71
|
-
#
|
|
72
|
-
#
|
|
94
|
+
# Applies the given block to the text outside quoted string literals
|
|
95
|
+
# only, copying every quoted run through byte-for-byte. Every rule
|
|
96
|
+
# here (collapse_whitespace, the `.length`→`.size` fold) must stay
|
|
97
|
+
# quote-aware: run it quote-blind and `"a b"` collapses to `"a b"`,
|
|
98
|
+
# `"a.length"` folds to `"a.size"`, just as readily as the real
|
|
99
|
+
# source outside the quotes — silently rewriting what a predicate
|
|
100
|
+
# compares a string attribute against, not merely how the predicate
|
|
101
|
+
# itself is spelled. A canonical string literal's contents are data,
|
|
102
|
+
# never syntax to normalise.
|
|
73
103
|
#
|
|
74
104
|
# Handles both `"` and `'` delimiters (this grammar's own
|
|
75
105
|
# `Resolver.quoted?` admits either), quote-aware exactly the way
|
|
76
106
|
# `Evaluator.top_level_index`/`Resolver.array_elements` already are
|
|
77
107
|
# elsewhere in this sublanguage. An unterminated quote (malformed
|
|
78
108
|
# input) is passed through raw rather than risk mangling it further.
|
|
109
|
+
#
|
|
110
|
+
# @param text [String] the source text to scan for quoted string
|
|
111
|
+
# literals
|
|
112
|
+
# @yieldparam segment [String] a run of `text` outside any quoted
|
|
113
|
+
# literal
|
|
114
|
+
# @yieldreturn [String] the transformed replacement for `segment`
|
|
115
|
+
# @return [String] `text` with the block applied to every unquoted
|
|
116
|
+
# run, and every quoted run (`"`- or `'`-delimited) copied through
|
|
117
|
+
# unchanged
|
|
79
118
|
def map_outside_strings(text)
|
|
80
119
|
result = +""
|
|
81
120
|
buffer = +""
|