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
data/lib/hecks/grammar/evolve.rb
CHANGED
|
@@ -23,6 +23,10 @@ module Hecks
|
|
|
23
23
|
# as `--foo`'s value (and `--bar` would then never be seen at
|
|
24
24
|
# all), and a value-less `--foo` at the end of argv would bypass
|
|
25
25
|
# whatever default `foo` promised instead of falling back to it.
|
|
26
|
+
# @param argv [Array<String>] the command-line argument list to scan
|
|
27
|
+
# @param name [String] the flag's name, without its leading `--`
|
|
28
|
+
# @param default [String, nil] value to use when the flag is absent or has no value
|
|
29
|
+
# @return [String, nil] the flag's value, or `default`
|
|
26
30
|
def option(argv, name, default = nil)
|
|
27
31
|
index = argv.index("--#{name}")
|
|
28
32
|
return default unless index
|
|
@@ -42,6 +46,12 @@ module Hecks
|
|
|
42
46
|
# the gate specs, not on anything this method knows about). A
|
|
43
47
|
# clean return leaves the snapshots unused; the caller decides
|
|
44
48
|
# from there whether the tree stands.
|
|
49
|
+
#
|
|
50
|
+
# @param paths [Array<String>] files to snapshot before running the block
|
|
51
|
+
# @yield the mutating work to run, restored from snapshot if it raises
|
|
52
|
+
# @return [Object] the block's result
|
|
53
|
+
# @raise [StandardError] re-raises whatever the block raised, after restoring
|
|
54
|
+
# every snapshotted file
|
|
45
55
|
def restore_on_raise(paths)
|
|
46
56
|
snapshots = paths.to_h { |path| [path, File.read(path)] }
|
|
47
57
|
yield
|
|
@@ -50,6 +60,10 @@ module Hecks
|
|
|
50
60
|
raise
|
|
51
61
|
end
|
|
52
62
|
|
|
63
|
+
# Every syntax-table file declaring a `KeywordSeed` or `ArgumentSeed`
|
|
64
|
+
# value object.
|
|
65
|
+
#
|
|
66
|
+
# @return [Array<String>] matching `.bluebook` file paths
|
|
53
67
|
def syntax_paths
|
|
54
68
|
Dir.glob(File.expand_path("../language/**/*.bluebook", __dir__)).select do |path|
|
|
55
69
|
source = File.read(path)
|
|
@@ -60,13 +74,25 @@ module Hecks
|
|
|
60
74
|
# Kept as a narrow compatibility door for callers deliberately doing
|
|
61
75
|
# single-file surgery. Normal operation uses `syntax_paths` and discovers
|
|
62
76
|
# the owning concept from the row itself.
|
|
77
|
+
#
|
|
78
|
+
# @return [String, nil] the first syntax-table path, or nil when there are none
|
|
63
79
|
def syntax_path = syntax_paths.first
|
|
64
80
|
|
|
81
|
+
# Resolves the file(s) a call should search or write.
|
|
82
|
+
#
|
|
83
|
+
# @param path [String, nil] an explicit single file, or nil for every syntax path
|
|
84
|
+
# @return [Array<String>] `[path]` when given, else `syntax_paths`
|
|
65
85
|
def paths_for(path) = path ? Array(path) : syntax_paths
|
|
66
86
|
|
|
67
87
|
# The Keyword one_of's member rows, parsed leniently off the text —
|
|
68
88
|
# enough to know each row's (word, context, status), which is all
|
|
69
89
|
# the tool ever asks.
|
|
90
|
+
#
|
|
91
|
+
# @param path [String, nil] an explicit single file to read, or nil for every
|
|
92
|
+
# syntax path
|
|
93
|
+
# @return [Array<Hash>] one Hash per member row, with `:word`, `:context`,
|
|
94
|
+
# `:status` (`"admitted"` when unspelled), and `:was` (nil unless renamed)
|
|
95
|
+
# @raise [Refusal] if `path` is given and declares no `KeywordSeed` value object
|
|
70
96
|
def keyword_rows(path = nil)
|
|
71
97
|
paths_for(path).flat_map do |candidate|
|
|
72
98
|
blocks = seed_blocks(File.read(candidate), "KeywordSeed")
|
|
@@ -82,6 +108,20 @@ module Hecks
|
|
|
82
108
|
end
|
|
83
109
|
end
|
|
84
110
|
|
|
111
|
+
# Declares a new, proposed keyword row in the syntax table that owns
|
|
112
|
+
# `context` (or `opens`'s own aggregate, for a `File`-context word).
|
|
113
|
+
#
|
|
114
|
+
# @param word [String] the keyword's spelling
|
|
115
|
+
# @param context [String] the grammar context the word is declared in
|
|
116
|
+
# @param body [String] the keyword's body shape; `"none"` by default
|
|
117
|
+
# @param inner [String] the keyword's inner shape, if any
|
|
118
|
+
# @param opens [String] the aggregate concept a `File`-context word opens
|
|
119
|
+
# @param fills [String] the field the keyword fills, if any
|
|
120
|
+
# @param path [String, nil] an explicit single file to search/write, or nil to
|
|
121
|
+
# search every syntax path
|
|
122
|
+
# @return [void]
|
|
123
|
+
# @raise [Refusal] if `context`.`word` is already declared, or no syntax table
|
|
124
|
+
# owns `context`
|
|
85
125
|
def propose(word:, context:, body: "none", inner: "", opens: "", fills: "", path: nil)
|
|
86
126
|
if keyword_rows(path).any? do |row|
|
|
87
127
|
row[:word] == word && row[:context] == context
|
|
@@ -105,6 +145,17 @@ module Hecks
|
|
|
105
145
|
File.write(path, source.sub(block, updated))
|
|
106
146
|
end
|
|
107
147
|
|
|
148
|
+
# Rewrites a declared keyword row's `status:` cell in place.
|
|
149
|
+
#
|
|
150
|
+
# @param word [String] the keyword's spelling
|
|
151
|
+
# @param context [String] the grammar context the word is declared in
|
|
152
|
+
# @param to [String] the new status: `"proposed"`, `"admitted"`, `"deprecated"`,
|
|
153
|
+
# or `"retired"`
|
|
154
|
+
# @param path [String, nil] an explicit single file to search/write, or nil to
|
|
155
|
+
# search every syntax path
|
|
156
|
+
# @return [void]
|
|
157
|
+
# @raise [Refusal] if `to` is not one of the four stations, or `context`.`word`
|
|
158
|
+
# is not declared
|
|
108
159
|
def set_status(word:, context:, to:, path: nil)
|
|
109
160
|
raise Refusal, "#{to.inspect} is not a station a word's life admits" unless %w[proposed admitted deprecated
|
|
110
161
|
retired].include?(to)
|
|
@@ -131,8 +182,17 @@ module Hecks
|
|
|
131
182
|
# `was:` — one hop only. Renaming an already-renamed word refuses
|
|
132
183
|
# until the language grows real eras for its own words; renaming
|
|
133
184
|
# onto a spelling the context already declares refuses too. The
|
|
134
|
-
# word's Argument rows follow it — row-aware
|
|
135
|
-
# substitution
|
|
185
|
+
# word's Argument rows follow it — row-aware, not a blind
|
|
186
|
+
# substitution (see `cascade_argument_rename`).
|
|
187
|
+
#
|
|
188
|
+
# @param word [String] the keyword's current spelling
|
|
189
|
+
# @param context [String] the grammar context the word is declared in
|
|
190
|
+
# @param to [String] the keyword's new spelling
|
|
191
|
+
# @param path [String, nil] an explicit single file to search/write, or nil to
|
|
192
|
+
# search every syntax path
|
|
193
|
+
# @return [void]
|
|
194
|
+
# @raise [Refusal] if `context`.`word` is not declared, was already renamed once,
|
|
195
|
+
# or `to` is already declared in `context`
|
|
136
196
|
def rename(word:, context:, to:, path: nil)
|
|
137
197
|
row = keyword_rows(path).find { |r| r[:word] == word && r[:context] == context }
|
|
138
198
|
raise Refusal, "#{context}.#{word} is not declared" unless row
|
|
@@ -160,16 +220,38 @@ module Hecks
|
|
|
160
220
|
cascade_argument_rename(keyword: word, context: context, to: to, path: paths)
|
|
161
221
|
end
|
|
162
222
|
|
|
223
|
+
# Tells whether `line` is a KeywordSeed member row for `(word, context)`.
|
|
224
|
+
#
|
|
225
|
+
# @param line [String] one raw source line
|
|
226
|
+
# @param word [String] the keyword's spelling
|
|
227
|
+
# @param context [String] the grammar context
|
|
228
|
+
# @return [Boolean]
|
|
163
229
|
def member_row?(line, word, context)
|
|
164
230
|
line =~ /^\s*member / && line.include?(%(word: "#{word}")) && line.include?(%(context: "#{context}"))
|
|
165
231
|
end
|
|
166
232
|
|
|
233
|
+
# Finds which syntax-table path declares a keyword row.
|
|
234
|
+
#
|
|
235
|
+
# @param word [String] the keyword's spelling
|
|
236
|
+
# @param context [String] the grammar context
|
|
237
|
+
# @param paths [Array<String>] candidate syntax-table paths to search
|
|
238
|
+
# @return [String] the path whose KeywordSeed declares `(word, context)`
|
|
239
|
+
# @raise [Refusal] if no path in `paths` declares that row
|
|
167
240
|
def path_holding_keyword(word, context, paths = syntax_paths)
|
|
168
241
|
paths.find do |candidate|
|
|
169
242
|
keyword_blocks(File.read(candidate)).any? { |block| block.lines.any? { |line| member_row?(line, word, context) } }
|
|
170
243
|
end || raise(Refusal, "#{context}.#{word} is not declared")
|
|
171
244
|
end
|
|
172
245
|
|
|
246
|
+
# Finds which syntax-table path declares an argument row.
|
|
247
|
+
#
|
|
248
|
+
# @param keyword [String] the argument's owning keyword
|
|
249
|
+
# @param context [String] the grammar context
|
|
250
|
+
# @param at [String] the argument's positional slot, `""` for a named-only argument
|
|
251
|
+
# @param named [String] the argument's keyword name, `""` for a positional-only argument
|
|
252
|
+
# @param paths [Array<String>] candidate syntax-table paths to search
|
|
253
|
+
# @return [String] the path whose ArgumentSeed declares this row
|
|
254
|
+
# @raise [Refusal] if no path in `paths` declares that row
|
|
173
255
|
def path_holding_argument(keyword, context, at, named, paths = syntax_paths)
|
|
174
256
|
paths.find do |candidate|
|
|
175
257
|
argument_blocks(File.read(candidate)).any? do |block|
|
|
@@ -183,6 +265,14 @@ module Hecks
|
|
|
183
265
|
# A new row belongs wherever that context's existing rows live. File is
|
|
184
266
|
# intentionally wider than one aggregate; for a new entry point, `opens`
|
|
185
267
|
# identifies the aggregate concept whose file should own it.
|
|
268
|
+
#
|
|
269
|
+
# @param context [String] the grammar context a new row is being added to
|
|
270
|
+
# @param word [String] the word being added, used only in the refusal message
|
|
271
|
+
# @param opens [String] the aggregate concept a `File`-context word opens
|
|
272
|
+
# @param paths [Array<String>] candidate syntax-table paths to search
|
|
273
|
+
# @return [String] the path that should own the new row
|
|
274
|
+
# @raise [Refusal] if no candidate path declares an existing row for `context`
|
|
275
|
+
# (and, for a `File` context with `opens` given, no path declares that aggregate)
|
|
186
276
|
def owner_path(context:, word:, opens: "", paths: syntax_paths)
|
|
187
277
|
if context == "File" && !opens.to_s.empty?
|
|
188
278
|
aggregate_path = paths.find { |candidate| File.read(candidate).match?(/^\s*aggregate "#{Regexp.escape(opens)}" do$/) }
|
|
@@ -197,6 +287,13 @@ module Hecks
|
|
|
197
287
|
end || raise(Refusal, "no aggregate-local syntax table owns context #{context.inspect} for #{word}")
|
|
198
288
|
end
|
|
199
289
|
|
|
290
|
+
# Every `value_object "<name>"` block's full source text, from its
|
|
291
|
+
# opener to its closing `end`.
|
|
292
|
+
#
|
|
293
|
+
# @param source [String] a `.bluebook` file's source text
|
|
294
|
+
# @param name [String] the value object's name, such as `"KeywordSeed"`
|
|
295
|
+
# @return [Array<String>] each matching block's raw source, including the
|
|
296
|
+
# opener and closing `end` lines
|
|
200
297
|
def seed_blocks(source, name)
|
|
201
298
|
opener = /^([ \t]*)value_object "#{Regexp.escape(name)}" do$/
|
|
202
299
|
source.to_enum(:scan, opener).map do
|
|
@@ -209,6 +306,15 @@ module Hecks
|
|
|
209
306
|
end
|
|
210
307
|
end
|
|
211
308
|
|
|
309
|
+
# The first `value_object "<name>"` block's source text.
|
|
310
|
+
#
|
|
311
|
+
# @param source [String] a `.bluebook` file's source text
|
|
312
|
+
# @param name [String] the value object's name, such as `"KeywordSeed"`
|
|
313
|
+
# @param required [Boolean] whether a missing block should raise instead of
|
|
314
|
+
# returning nil
|
|
315
|
+
# @return [String, nil] the block's raw source, or nil when absent and not
|
|
316
|
+
# `required`
|
|
317
|
+
# @raise [Refusal] if `required` and `source` declares no such value object
|
|
212
318
|
def seed_block(source, name, required: false)
|
|
213
319
|
block = seed_blocks(source, name).first
|
|
214
320
|
unless block
|
|
@@ -225,7 +331,15 @@ module Hecks
|
|
|
225
331
|
# is the value object's own, the same fact the original one_of-
|
|
226
332
|
# nested version of this method leaned on (nothing else nested
|
|
227
333
|
# inside it either, before or after).
|
|
334
|
+
# @param source [String] a `.bluebook` file's source text
|
|
335
|
+
# @return [Array<String>] each KeywordSeed block's raw source
|
|
228
336
|
def keyword_blocks(source) = seed_blocks(source, "KeywordSeed")
|
|
337
|
+
|
|
338
|
+
# The first KeywordSeed block's source text.
|
|
339
|
+
#
|
|
340
|
+
# @param source [String] a `.bluebook` file's source text
|
|
341
|
+
# @return [String] the first KeywordSeed block's raw source
|
|
342
|
+
# @raise [Refusal] if `source` declares no KeywordSeed value object
|
|
229
343
|
def keyword_block(source) = seed_block(source, "KeywordSeed", required: true)
|
|
230
344
|
|
|
231
345
|
# ── the Argument rows — a word's own arguments, at last with tooling
|
|
@@ -234,6 +348,14 @@ module Hecks
|
|
|
234
348
|
# kwarg), so identity here is the full (keyword, context, at, named)
|
|
235
349
|
# tuple, not the two-field key a Keyword row answers to.
|
|
236
350
|
|
|
351
|
+
# The ArgumentSeed's member rows, parsed leniently off the text.
|
|
352
|
+
#
|
|
353
|
+
# @param path [String, nil] an explicit single file to read, or nil for every
|
|
354
|
+
# syntax path
|
|
355
|
+
# @return [Array<Hash>] one Hash per member row, with `:keyword`, `:context`,
|
|
356
|
+
# `:at`, `:named`, `:kind`, `:required`, `:fills`, and `:status`
|
|
357
|
+
# (`"admitted"` when unspelled)
|
|
358
|
+
# @raise [Refusal] if `path` is given and declares no `ArgumentSeed` value object
|
|
237
359
|
def argument_rows(path = nil)
|
|
238
360
|
paths_for(path).flat_map do |candidate|
|
|
239
361
|
blocks = seed_blocks(File.read(candidate), "ArgumentSeed")
|
|
@@ -257,6 +379,20 @@ module Hecks
|
|
|
257
379
|
# naming a single field as a row that "names a single field, which
|
|
258
380
|
# it cannot fill" — correctly, since the two shapes are genuinely
|
|
259
381
|
# different and only one of them can be checked the same way.
|
|
382
|
+
# @param keyword [String] the argument's owning keyword
|
|
383
|
+
# @param context [String] the grammar context
|
|
384
|
+
# @param kind [String] the argument's value kind
|
|
385
|
+
# @param required [String] `"true"` or `"false"`, as text like every other cell
|
|
386
|
+
# @param at [String] the argument's positional slot, `""` for a named-only argument
|
|
387
|
+
# @param named [String] the argument's keyword name, `""` for a positional-only argument
|
|
388
|
+
# @param fills [String] the field the argument fills, if any
|
|
389
|
+
# @param pairs_shape [String, nil] the shape a `pairs` argument's key/value list
|
|
390
|
+
# fills, or nil when this argument is not a `pairs` argument
|
|
391
|
+
# @param path [String, nil] an explicit single file to search/write, or nil to
|
|
392
|
+
# search every syntax path
|
|
393
|
+
# @return [void]
|
|
394
|
+
# @raise [Refusal] if this (keyword, context, at, named) row is already declared,
|
|
395
|
+
# or no syntax table owns `context`
|
|
260
396
|
def propose_argument(keyword:, context:, kind:, required: "false", at: "", named: "", fills: "",
|
|
261
397
|
pairs_shape: nil, path: nil)
|
|
262
398
|
if argument_rows(path).any? { |r| argument_identity(r) == [keyword, context, at, named] }
|
|
@@ -280,6 +416,18 @@ module Hecks
|
|
|
280
416
|
File.write(path, source.sub(block, updated))
|
|
281
417
|
end
|
|
282
418
|
|
|
419
|
+
# Rewrites a declared argument row's `status:` cell in place.
|
|
420
|
+
#
|
|
421
|
+
# @param keyword [String] the argument's owning keyword
|
|
422
|
+
# @param context [String] the grammar context
|
|
423
|
+
# @param to [String] the new status: `"proposed"`, `"admitted"`, `"deprecated"`,
|
|
424
|
+
# or `"retired"`
|
|
425
|
+
# @param at [String] the argument's positional slot, `""` for a named-only argument
|
|
426
|
+
# @param named [String] the argument's keyword name, `""` for a positional-only argument
|
|
427
|
+
# @param path [String, nil] an explicit single file to search/write, or nil to
|
|
428
|
+
# search every syntax path
|
|
429
|
+
# @return [void]
|
|
430
|
+
# @raise [Refusal] if `to` is not one of the four stations, or the row is not declared
|
|
283
431
|
def set_argument_status(keyword:, context:, to:, at: "", named: "", path: nil)
|
|
284
432
|
raise Refusal, "#{to.inspect} is not a station an argument's life admits" unless %w[proposed admitted deprecated
|
|
285
433
|
retired].include?(to)
|
|
@@ -307,12 +455,26 @@ module Hecks
|
|
|
307
455
|
File.write(path, source.sub(block, updated))
|
|
308
456
|
end
|
|
309
457
|
|
|
458
|
+
# Tells whether `line` is an ArgumentSeed member row for this
|
|
459
|
+
# (keyword, context, at, named) tuple.
|
|
460
|
+
#
|
|
461
|
+
# @param line [String] one raw source line
|
|
462
|
+
# @param keyword [String] the argument's owning keyword
|
|
463
|
+
# @param context [String] the grammar context
|
|
464
|
+
# @param at [String] the argument's positional slot
|
|
465
|
+
# @param named [String] the argument's keyword name
|
|
466
|
+
# @return [Boolean]
|
|
310
467
|
def argument_row?(line, keyword, context, at, named)
|
|
311
468
|
line =~ /^\s*member / &&
|
|
312
469
|
line.include?(%(keyword: "#{keyword}")) && line.include?(%(context: "#{context}")) &&
|
|
313
470
|
line.include?(%(at: "#{at}")) && line.include?(%(named: "#{named}"))
|
|
314
471
|
end
|
|
315
472
|
|
|
473
|
+
# The identity tuple an argument row is keyed by.
|
|
474
|
+
#
|
|
475
|
+
# @param row [Hash] an argument row, as `argument_rows` returns one
|
|
476
|
+
# @return [Array(String, String, String, String)] the row's `(keyword, context,
|
|
477
|
+
# at, named)` identity tuple
|
|
316
478
|
def argument_identity(row) = [row[:keyword], row[:context], row[:at], row[:named]]
|
|
317
479
|
|
|
318
480
|
# The rename cascade, row-aware — only the rows that actually belong
|
|
@@ -320,6 +482,12 @@ module Hecks
|
|
|
320
482
|
# blind `gsub` on every `keyword: "word",` substring in the file
|
|
321
483
|
# (which a coincidentally-matching row elsewhere could have
|
|
322
484
|
# corrupted, and which read nothing before writing).
|
|
485
|
+
# @param keyword [String] the keyword whose argument rows follow its rename
|
|
486
|
+
# @param context [String] the grammar context
|
|
487
|
+
# @param to [String] the keyword's new spelling
|
|
488
|
+
# @param path [String, nil] an explicit single file to search/write, or nil to
|
|
489
|
+
# search every syntax path
|
|
490
|
+
# @return [void]
|
|
323
491
|
def cascade_argument_rename(keyword:, context:, to:, path: nil)
|
|
324
492
|
paths_for(path).each do |candidate|
|
|
325
493
|
source = File.read(candidate)
|
|
@@ -341,7 +509,15 @@ module Hecks
|
|
|
341
509
|
# `keyword_block`'s own comment for why the first bare `end` after
|
|
342
510
|
# the opener is already the right one, now that `member` rows sit
|
|
343
511
|
# bare (S3, ADR 0025).
|
|
512
|
+
# @param source [String] a `.bluebook` file's source text
|
|
513
|
+
# @return [Array<String>] each ArgumentSeed block's raw source
|
|
344
514
|
def argument_blocks(source) = seed_blocks(source, "ArgumentSeed")
|
|
515
|
+
|
|
516
|
+
# The first ArgumentSeed block's source text.
|
|
517
|
+
#
|
|
518
|
+
# @param source [String] a `.bluebook` file's source text
|
|
519
|
+
# @return [String] the first ArgumentSeed block's raw source
|
|
520
|
+
# @raise [Refusal] if `source` declares no ArgumentSeed value object
|
|
345
521
|
def argument_block(source) = seed_block(source, "ArgumentSeed", required: true)
|
|
346
522
|
end
|
|
347
523
|
end
|
data/lib/hecks/grammar.rb
CHANGED
|
@@ -27,6 +27,11 @@ module Hecks
|
|
|
27
27
|
# its real commands. A refused step raises — a generator running off
|
|
28
28
|
# a half-admitted ledger would project a table the gates never
|
|
29
29
|
# accepted.
|
|
30
|
+
#
|
|
31
|
+
# @return [Runtime::Dispatcher] the dispatcher bound to the booted, replayed
|
|
32
|
+
# expression chapter
|
|
33
|
+
# @raise [Runtime::WiringError] if a ledger step is refused by the chapter's
|
|
34
|
+
# own domain rules
|
|
30
35
|
def expression
|
|
31
36
|
registry = Runtime::Registry.new
|
|
32
37
|
root = File.expand_path("../..", __dir__)
|
|
@@ -52,6 +57,13 @@ module Hecks
|
|
|
52
57
|
dispatcher
|
|
53
58
|
end
|
|
54
59
|
|
|
60
|
+
# Reads every admitted operator from the ledger's replayed chapter.
|
|
61
|
+
#
|
|
62
|
+
# @param dispatcher [Runtime::Dispatcher] a dispatcher bound to the booted
|
|
63
|
+
# expression chapter; defaults to booting and replaying a fresh one
|
|
64
|
+
# @return [Array<Hash>] one Hash per admitted operator, with `:symbol`,
|
|
65
|
+
# `:category`, `:precedence`, `:arity`, and `:renderings` (an Array of
|
|
66
|
+
# `{target:, form:}` Hashes)
|
|
55
67
|
def admitted_operators(dispatcher = expression)
|
|
56
68
|
records(dispatcher, "Operator").select { |op| op[:status] == "admitted" }.map do |op|
|
|
57
69
|
{ symbol: op[:symbol].value, category: op[:category].value,
|
|
@@ -60,6 +72,13 @@ module Hecks
|
|
|
60
72
|
end
|
|
61
73
|
end
|
|
62
74
|
|
|
75
|
+
# Reads every admitted normalisation rule from the ledger's replayed
|
|
76
|
+
# chapter, in position order.
|
|
77
|
+
#
|
|
78
|
+
# @param dispatcher [Runtime::Dispatcher] a dispatcher bound to the booted
|
|
79
|
+
# expression chapter; defaults to booting and replaying a fresh one
|
|
80
|
+
# @return [Array<Hash>] one Hash per admitted rule, with `:strategy`,
|
|
81
|
+
# `:source_token`, `:replacement`, `:boundary`, and `:position`
|
|
63
82
|
def admitted_normalisations(dispatcher = expression)
|
|
64
83
|
records(dispatcher, "Normalisation")
|
|
65
84
|
.select { |rule| rule[:status] == "admitted" }
|
|
@@ -71,6 +90,14 @@ module Hecks
|
|
|
71
90
|
end
|
|
72
91
|
end
|
|
73
92
|
|
|
93
|
+
# Reads every record of one aggregate from the expression chapter's own
|
|
94
|
+
# repository.
|
|
95
|
+
#
|
|
96
|
+
# @param dispatcher [Runtime::Dispatcher] a dispatcher bound to the booted
|
|
97
|
+
# expression chapter
|
|
98
|
+
# @param aggregate_name [String] the aggregate's declared name, such as
|
|
99
|
+
# `"Operator"`
|
|
100
|
+
# @return [Array<Runtime::Instance>] every stored instance of the aggregate
|
|
74
101
|
def records(dispatcher, aggregate_name)
|
|
75
102
|
registry = dispatcher.registry
|
|
76
103
|
aggregate = registry.bluebook("Expression").aggregate(aggregate_name)
|
|
@@ -87,6 +114,10 @@ module Hecks
|
|
|
87
114
|
# derives the set, with a usage site per operator, so the generator
|
|
88
115
|
# and the conformance spec can refuse the retirement by name instead
|
|
89
116
|
# of wedging.
|
|
117
|
+
#
|
|
118
|
+
# @return [Hash{String => Array<String>}] each self-bearing operator symbol
|
|
119
|
+
# mapped to the `"Chapter Aggregate.command"`/`"Chapter Aggregate::ValueObject"`
|
|
120
|
+
# sites that use it
|
|
90
121
|
def self_bearing_operators
|
|
91
122
|
sites = Hash.new { |h, k| h[k] = [] }
|
|
92
123
|
|
|
@@ -118,6 +149,9 @@ module Hecks
|
|
|
118
149
|
|
|
119
150
|
# Every grammar/*.bluebook chapter, booted the same way the corpus
|
|
120
151
|
# boots them — each alone, in a scratch registry.
|
|
152
|
+
#
|
|
153
|
+
# @return [Array<Class>] each grammar chapter, booted alone in its own scratch
|
|
154
|
+
# registry
|
|
121
155
|
def grammar_chapters
|
|
122
156
|
Dir[File.join(DIR, "*.bluebook")].map do |chapter|
|
|
123
157
|
registry = Runtime::Registry.new
|
|
@@ -136,6 +170,10 @@ module Hecks
|
|
|
136
170
|
# Which admitted operators one canonical text evaluates through —
|
|
137
171
|
# the evaluator's own parse, walked for its operator nodes, leaves
|
|
138
172
|
# walked for the resolver's arithmetic.
|
|
173
|
+
#
|
|
174
|
+
# @param canonical [String] canonical expression text to parse
|
|
175
|
+
# @return [Array<String>] operator symbols the expression evaluates through, or
|
|
176
|
+
# `[]` when `canonical` fails to parse
|
|
139
177
|
def operators_in(canonical)
|
|
140
178
|
evaluator = Bluebook::Expression::Evaluator
|
|
141
179
|
begin
|
|
@@ -155,6 +193,10 @@ module Hecks
|
|
|
155
193
|
# would trade one place that shows the whole operator vocabulary for
|
|
156
194
|
# several that each show a fragment, with no reduction in real
|
|
157
195
|
# complexity.
|
|
196
|
+
# @param node [Object] an evaluator/resolver AST node, or a Struct fallback
|
|
197
|
+
# @param evaluator [Module] `Bluebook::Expression::Evaluator`, passed through
|
|
198
|
+
# so nested calls don't re-resolve the constant
|
|
199
|
+
# @return [Array<String>] operator symbols found in `node` and its children
|
|
158
200
|
# rubocop:disable-next Metrics/AbcSize
|
|
159
201
|
def walk_operators(node, evaluator)
|
|
160
202
|
resolver = Bluebook::Expression::Resolver
|
|
@@ -177,6 +219,10 @@ module Hecks
|
|
|
177
219
|
end
|
|
178
220
|
end
|
|
179
221
|
|
|
222
|
+
# Deep-symbolizes a JSON-decoded value's Hash keys.
|
|
223
|
+
#
|
|
224
|
+
# @param value [Object] a Hash, Array, or scalar decoded from JSON
|
|
225
|
+
# @return [Object] `value` with every Hash key (recursively) converted to a Symbol
|
|
180
226
|
def symbolize(value)
|
|
181
227
|
case value
|
|
182
228
|
when Hash then value.to_h { |k, v| [k.to_sym, symbolize(v)] }
|
data/lib/hecks/ir.rb
CHANGED
|
@@ -17,13 +17,16 @@ module Hecks
|
|
|
17
17
|
# nesting everything a chapter declares. It is deliberately not named
|
|
18
18
|
# after this, its own output.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
20
|
+
# ## Why declared, not hand-written
|
|
21
|
+
#
|
|
22
|
+
# A hand-written `to_h` per construct would say the same four things in the
|
|
23
|
+
# same order — read a field, recurse into a child, recurse into a list, or
|
|
24
|
+
# compute something — leaving a construct's shape knowable only by reading a
|
|
25
|
+
# method body. Declared, it is data: `ir_spec` can be walked by anything
|
|
26
|
+
# that wants to know what a construct carries, which is the whole point of
|
|
27
|
+
# hanging emission off the model rather than burying it.
|
|
28
|
+
#
|
|
29
|
+
# ## Usage
|
|
27
30
|
#
|
|
28
31
|
# include Hecks::IR # an instance-shaped construct
|
|
29
32
|
#
|
|
@@ -57,11 +60,20 @@ module Hecks
|
|
|
57
60
|
# extend Hecks::IR # class-shaped — to_h is a class method
|
|
58
61
|
#
|
|
59
62
|
# Both get the same `emits_ir` and the same emission rules.
|
|
63
|
+
#
|
|
64
|
+
# Wires an instance-shaped construct's declaration and emission sides in.
|
|
65
|
+
#
|
|
66
|
+
# @param base [Class, Module] the includer
|
|
67
|
+
# @return [void]
|
|
60
68
|
def self.included(base)
|
|
61
69
|
base.extend(Declares)
|
|
62
70
|
base.include(Emits)
|
|
63
71
|
end
|
|
64
72
|
|
|
73
|
+
# Wires a class-shaped construct's declaration and emission sides in.
|
|
74
|
+
#
|
|
75
|
+
# @param base [Class, Module] the extender
|
|
76
|
+
# @return [void]
|
|
65
77
|
def self.extended(base)
|
|
66
78
|
base.extend(Declares)
|
|
67
79
|
base.extend(Emits)
|
|
@@ -79,17 +91,36 @@ module Hecks
|
|
|
79
91
|
# back — walking the superclass chain so an anonymous `Class.new(base)`
|
|
80
92
|
# inherits its base's shape instead of redeclaring it.
|
|
81
93
|
module Declares
|
|
94
|
+
# Records this construct's field -> emission-rule map.
|
|
95
|
+
#
|
|
96
|
+
# @param spec [Hash{Symbol => Symbol, Many, One, Proc}] each emitted key,
|
|
97
|
+
# mapped to how it is produced: a Symbol is sent to the construct, `many`/
|
|
98
|
+
# `one` recurse into nested constructs, and a Proc is instance-`exec`'d
|
|
99
|
+
# @return [void]
|
|
82
100
|
def emits_ir(**spec)
|
|
83
101
|
@ir_spec = spec
|
|
84
102
|
end
|
|
85
103
|
|
|
104
|
+
# Marks a field as a list of nested constructs, each emitting itself.
|
|
105
|
+
#
|
|
106
|
+
# @param source [Symbol] the method that returns the list
|
|
107
|
+
# @return [Many] the wrapped source, for `emits_ir`
|
|
86
108
|
def many(source) = Many.new(source)
|
|
109
|
+
|
|
110
|
+
# Marks a field as one nested construct, or nothing.
|
|
111
|
+
#
|
|
112
|
+
# @param source [Symbol] the method that returns the construct, or nil
|
|
113
|
+
# @return [One] the wrapped source, for `emits_ir`
|
|
87
114
|
def one(source) = One.new(source)
|
|
88
115
|
|
|
89
116
|
# Walks the superclass chain so a `Class.new(ValueObject)` — which
|
|
90
117
|
# is what every declared value object actually is — inherits the
|
|
91
118
|
# shape its base declared, rather than each anonymous subclass
|
|
92
119
|
# having to redeclare it.
|
|
120
|
+
#
|
|
121
|
+
# @return [Hash{Symbol => Symbol, Many, One, Proc}, nil] the field -> rule
|
|
122
|
+
# map declared by `emits_ir`, inherited from the nearest superclass that
|
|
123
|
+
# declared one; nil if nothing in the chain ever declared a shape
|
|
93
124
|
def ir_spec
|
|
94
125
|
return @ir_spec if defined?(@ir_spec) && @ir_spec
|
|
95
126
|
|
|
@@ -100,6 +100,16 @@ Hecks.bluebook "Hecksagon" do
|
|
|
100
100
|
member word: "uses_framework", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "framework_members"
|
|
101
101
|
member word: "uses_embryonaut_bluebook", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "vendored_bluebooks"
|
|
102
102
|
member word: "port", context: "Hecksagon", body: "keywords", inner: "DomainPort", opens: "DomainPort", fills: "", calls: "port_impl"
|
|
103
|
+
# A CROSS-DOMAIN REACTION, WIRED HERE INSTEAD OF THE BLUEBOOK — same
|
|
104
|
+
# underlying `Policy` shape a `policy` block already builds
|
|
105
|
+
# (`inner`/`opens: "Policy"` reuses that context's own `on`/`trigger`
|
|
106
|
+
# wholesale, zero new inner vocabulary), but declared as a wiring
|
|
107
|
+
# decision (which FOREIGN domain's event this chapter conforms to),
|
|
108
|
+
# the same kind of decision `port`/`uses_framework` already are.
|
|
109
|
+
# No `calls:` — `HecksagonBuilder#translates` is an ordinary method,
|
|
110
|
+
# the same shape `subscribe`/`uses_framework` already are, reached
|
|
111
|
+
# by Ruby's own method lookup before `word_gate` ever runs.
|
|
112
|
+
member word: "translates", context: "Hecksagon", body: "keywords", inner: "Policy", opens: "Policy", fills: ""
|
|
103
113
|
|
|
104
114
|
end
|
|
105
115
|
|
|
@@ -125,6 +135,7 @@ Hecks.bluebook "Hecksagon" do
|
|
|
125
135
|
member keyword: "uses_framework", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "framework_members"
|
|
126
136
|
member keyword: "uses_embryonaut_bluebook", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "vendored_bluebooks"
|
|
127
137
|
member keyword: "port", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "name"
|
|
138
|
+
member keyword: "translates", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "name"
|
|
128
139
|
|
|
129
140
|
end
|
|
130
141
|
end
|
data/lib/hecks/literal.rb
CHANGED
|
@@ -43,6 +43,12 @@ module Hecks
|
|
|
43
43
|
module Literal
|
|
44
44
|
module_function
|
|
45
45
|
|
|
46
|
+
# Turns a Ruby value into its self-describing wire spelling.
|
|
47
|
+
#
|
|
48
|
+
# @param value [Object] value to render: nil, Symbol, String, StateRef, true,
|
|
49
|
+
# false, Integer, Float, Hash, or Array (recursively)
|
|
50
|
+
# @return [String] the self-describing spelling `read` can parse back
|
|
51
|
+
# @raise [ArgumentError] if `value` is a type with no pinned literal spelling
|
|
46
52
|
def render(value)
|
|
47
53
|
case value
|
|
48
54
|
when nil then "nil"
|
|
@@ -67,6 +73,9 @@ module Hecks
|
|
|
67
73
|
# `return`; splitting them into named predicates would just rename
|
|
68
74
|
# each line without changing what it does, and would separate this
|
|
69
75
|
# method from the `render` it is the deliberate mirror of.
|
|
76
|
+
# @param text [String, #to_s] wire spelling produced by `render`, or a bare word
|
|
77
|
+
# @return [Object] nil, true, false, Integer, Float, Symbol, StateRef, String,
|
|
78
|
+
# Hash, or Array — or `text` itself, stripped, when it matches no known spelling
|
|
70
79
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
71
80
|
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
72
81
|
def read(text)
|
|
@@ -87,12 +96,28 @@ module Hecks
|
|
|
87
96
|
|
|
88
97
|
ESCAPED = { '"' => '\\"', "\\" => "\\\\" }.freeze
|
|
89
98
|
|
|
99
|
+
# Wraps `text` in double quotes, escaping embedded quotes and backslashes.
|
|
100
|
+
#
|
|
101
|
+
# @param text [String] raw text to quote
|
|
102
|
+
# @return [String] the quoted, escaped spelling
|
|
90
103
|
def quote(text) = "\"#{text.gsub(/["\\]/) { |char| ESCAPED[char] }}\""
|
|
91
104
|
|
|
105
|
+
# Tells whether `raw` is a double-quoted literal.
|
|
106
|
+
#
|
|
107
|
+
# @param raw [String] wire text to check
|
|
108
|
+
# @return [Boolean]
|
|
92
109
|
def quoted?(raw) = raw.length >= 2 && raw.start_with?('"') && raw.end_with?('"')
|
|
93
110
|
|
|
111
|
+
# Strips the surrounding quotes from a quoted literal and unescapes it.
|
|
112
|
+
#
|
|
113
|
+
# @param raw [String] a quoted literal, as `quoted?` would confirm
|
|
114
|
+
# @return [String] the unescaped text inside the quotes
|
|
94
115
|
def unquote(raw) = raw[1..-2].gsub(/\\(.)/) { ::Regexp.last_match(1) }
|
|
95
116
|
|
|
117
|
+
# Parses a `{key: value, ...}` wire literal.
|
|
118
|
+
#
|
|
119
|
+
# @param raw [String] text starting with `{` and ending with `}`
|
|
120
|
+
# @return [Hash{Symbol => Object}] the parsed hash, values read recursively via `read`
|
|
96
121
|
def read_hash(raw)
|
|
97
122
|
split_items(raw[1..-2]).to_h do |item|
|
|
98
123
|
key, _, held = item.partition(":")
|
|
@@ -100,12 +125,19 @@ module Hecks
|
|
|
100
125
|
end
|
|
101
126
|
end
|
|
102
127
|
|
|
128
|
+
# Parses a `[value, ...]` wire literal.
|
|
129
|
+
#
|
|
130
|
+
# @param raw [String] text starting with `[` and ending with `]`
|
|
131
|
+
# @return [Array<Object>] the parsed values, each read recursively via `read`
|
|
103
132
|
def read_array(raw) = split_items(raw[1..-2]).map { |item| read(item) }
|
|
104
133
|
|
|
105
134
|
# Split on the commas that are actually separators — never one inside a
|
|
106
135
|
# quoted string or a nested brace/bracket. Scanned rather than
|
|
107
136
|
# `String#split(", ")`, which tore `"a, b"` in half and lost the second
|
|
108
137
|
# field of anything nested.
|
|
138
|
+
#
|
|
139
|
+
# @param body [String] the text between a literal's outer braces or brackets
|
|
140
|
+
# @return [Array<String>] each item's raw text, stripped, with empty items dropped
|
|
109
141
|
def split_items(body)
|
|
110
142
|
items = []
|
|
111
143
|
current = +""
|