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.
Files changed (267) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +65 -0
  3. data/lib/hecks/adapters/driven/folder.rb +73 -0
  4. data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
  5. data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
  6. data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
  7. data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
  8. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  9. data/lib/hecks/adapters/driven/heki.rb +93 -2
  10. data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
  12. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  13. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  14. data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
  15. data/lib/hecks/adapters/driven/lambda.rb +43 -2
  16. data/lib/hecks/adapters/driven/local_storage.rb +67 -1
  17. data/lib/hecks/adapters/driven/memory.rb +13 -13
  18. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
  19. data/lib/hecks/adapters/driven/prism.rb +46 -7
  20. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  21. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  22. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  23. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  24. data/lib/hecks/adapters/driven.rb +2 -0
  25. data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
  26. data/lib/hecks/behaviors/dsl.rb +58 -0
  27. data/lib/hecks/behaviors/expectations.rb +168 -7
  28. data/lib/hecks/behaviors/ir.rb +11 -0
  29. data/lib/hecks/behaviors/rspec.rb +8 -0
  30. data/lib/hecks/behaviors/runner.rb +19 -0
  31. data/lib/hecks/behaviors.rb +8 -0
  32. data/lib/hecks/bluebook/aggregate.rb +30 -0
  33. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
  34. data/lib/hecks/bluebook/assembly/build.rb +25 -4
  35. data/lib/hecks/bluebook/assembly/contract.rb +66 -14
  36. data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
  37. data/lib/hecks/bluebook/assembly/marks.rb +143 -14
  38. data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
  39. data/lib/hecks/bluebook/assembly.rb +25 -7
  40. data/lib/hecks/bluebook/attribute.rb +17 -3
  41. data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
  42. data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
  43. data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
  44. data/lib/hecks/bluebook/behaviour/command.rb +33 -3
  45. data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
  46. data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
  47. data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
  48. data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
  49. data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
  50. data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
  51. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  52. data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
  53. data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
  54. data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
  55. data/lib/hecks/bluebook/chapter.rb +23 -0
  56. data/lib/hecks/bluebook/command.rb +53 -8
  57. data/lib/hecks/bluebook/domain_port.rb +25 -0
  58. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
  59. data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
  60. data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
  62. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
  63. data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
  64. data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
  65. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
  66. data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
  67. data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
  68. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
  69. data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
  70. data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
  71. data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
  72. data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
  73. data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
  74. data/lib/hecks/bluebook/entity.rb +29 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
  77. data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
  78. data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
  79. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
  80. data/lib/hecks/bluebook/expression/resolver.rb +320 -77
  81. data/lib/hecks/bluebook/hexagon.rb +34 -0
  82. data/lib/hecks/bluebook/lifecycle.rb +11 -0
  83. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
  84. data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
  85. data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
  86. data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
  88. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
  90. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
  91. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
  92. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
  93. data/lib/hecks/bluebook/meta_validator.rb +180 -84
  94. data/lib/hecks/bluebook/model_check.rb +268 -24
  95. data/lib/hecks/bluebook/pattern_subset.rb +23 -1
  96. data/lib/hecks/bluebook/process_manager.rb +13 -0
  97. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  98. data/lib/hecks/bluebook/project_loader.rb +40 -0
  99. data/lib/hecks/bluebook/project_register.rb +44 -0
  100. data/lib/hecks/bluebook/query.rb +27 -0
  101. data/lib/hecks/bluebook/read_model.rb +21 -1
  102. data/lib/hecks/bluebook/reference.rb +21 -8
  103. data/lib/hecks/bluebook/smoke_test.rb +29 -6
  104. data/lib/hecks/bluebook/synthesizer.rb +34 -0
  105. data/lib/hecks/bluebook/translation.rb +30 -1
  106. data/lib/hecks/bluebook/value_object.rb +23 -5
  107. data/lib/hecks/bluebook.rb +3 -4
  108. data/lib/hecks/codemod.rb +107 -20
  109. data/lib/hecks/construct.rb +15 -1
  110. data/lib/hecks/corpus.rb +146 -25
  111. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  112. data/lib/hecks/doc/reference.rb +185 -16
  113. data/lib/hecks/embryonaut_bluebook.rb +32 -9
  114. data/lib/hecks/facade/handle.rb +76 -3
  115. data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
  116. data/lib/hecks/forms/field_shape.rb +3 -0
  117. data/lib/hecks/forms/page.rb +14 -0
  118. data/lib/hecks/forms/port_argument.rb +12 -0
  119. data/lib/hecks/forms/query_form_renderer.rb +63 -0
  120. data/lib/hecks/forms/record_renderer.rb +58 -0
  121. data/lib/hecks/forms/record_table.rb +27 -0
  122. data/lib/hecks/forms/reference_options.rb +24 -0
  123. data/lib/hecks/forms/value_object_shape.rb +10 -0
  124. data/lib/hecks/fqn.rb +58 -0
  125. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  126. data/lib/hecks/framework/oidc.json +15 -0
  127. data/lib/hecks/framework.rb +43 -20
  128. data/lib/hecks/freezer.rb +17 -1
  129. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
  130. data/lib/hecks/fuzzing/combination_miner.rb +59 -0
  131. data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
  132. data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
  133. data/lib/hecks/fuzzing/differential.rb +34 -0
  134. data/lib/hecks/fuzzing/domain_generator.rb +188 -11
  135. data/lib/hecks/fuzzing/era_boundary.rb +45 -15
  136. data/lib/hecks/fuzzing/form_census.rb +86 -0
  137. data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
  138. data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
  139. data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
  140. data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
  141. data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
  142. data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
  143. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
  144. data/lib/hecks/fuzzing/properties/guards.rb +44 -0
  145. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
  146. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
  147. data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
  148. data/lib/hecks/fuzzing/properties/querying.rb +68 -14
  149. data/lib/hecks/fuzzing/properties.rb +24 -15
  150. data/lib/hecks/fuzzing/qa_settings.rb +12 -0
  151. data/lib/hecks/fuzzing/replay.rb +137 -29
  152. data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
  153. data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
  154. data/lib/hecks/fuzzing/self_consistency.rb +189 -40
  155. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
  156. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
  157. data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
  158. data/lib/hecks/fuzzing/shrinker.rb +114 -2
  159. data/lib/hecks/fuzzing/structural_skips.rb +18 -4
  160. data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
  161. data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
  162. data/lib/hecks/fuzzing/value_generator.rb +98 -10
  163. data/lib/hecks/grammar/evolve.rb +178 -2
  164. data/lib/hecks/grammar.rb +46 -0
  165. data/lib/hecks/ir.rb +38 -7
  166. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  167. data/lib/hecks/literal.rb +32 -0
  168. data/lib/hecks/naming.rb +88 -7
  169. data/lib/hecks/ports/access_control.rb +5 -10
  170. data/lib/hecks/ports/authorization.rb +3 -6
  171. data/lib/hecks/ports/identity_assignment.rb +1 -2
  172. data/lib/hecks/ports/identity_resolution.rb +1 -2
  173. data/lib/hecks/ports/key_vault.port +6 -0
  174. data/lib/hecks/ports/key_vault.rb +58 -0
  175. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
  176. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
  177. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
  178. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
  179. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
  180. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  181. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
  182. data/lib/hecks/ports/query/in_memory.rb +39 -6
  183. data/lib/hecks/ports/query/ordering.rb +15 -0
  184. data/lib/hecks/ports.rb +1 -0
  185. data/lib/hecks/projections/bootstrap_table.rb +43 -8
  186. data/lib/hecks/projections/diagrams.rb +243 -7
  187. data/lib/hecks/projections/glossary/html.rb +88 -0
  188. data/lib/hecks/projections/glossary/markdown.rb +47 -0
  189. data/lib/hecks/projections/glossary/mermaid.rb +48 -0
  190. data/lib/hecks/projections/glossary/sentences.rb +105 -0
  191. data/lib/hecks/projections/glossary.rb +161 -19
  192. data/lib/hecks/projections/model/deviations.rb +44 -0
  193. data/lib/hecks/projections/model.rb +51 -1
  194. data/lib/hecks/projections/oidc.rb +40 -3
  195. data/lib/hecks/projections/parser_table.rb +49 -8
  196. data/lib/hecks/projections/reference.rb +12 -0
  197. data/lib/hecks/projections/rust_vocabulary.rb +219 -16
  198. data/lib/hecks/projections/shape.rb +8 -0
  199. data/lib/hecks/projections/statements.rb +63 -16
  200. data/lib/hecks/projections/vocabulary.rb +17 -0
  201. data/lib/hecks/projector/cli_projector.rb +218 -10
  202. data/lib/hecks/projector/docs_projector.rb +145 -19
  203. data/lib/hecks/projector/exporter.rb +65 -11
  204. data/lib/hecks/projector/ir_projector.rb +6 -0
  205. data/lib/hecks/projector/narrate_projector.rb +136 -15
  206. data/lib/hecks/projector/target.rb +29 -5
  207. data/lib/hecks/projector.rb +74 -6
  208. data/lib/hecks/query_ir.rb +47 -0
  209. data/lib/hecks/query_specification/common/null_policy.rb +5 -3
  210. data/lib/hecks/rendering.rb +6 -0
  211. data/lib/hecks/router/namespace_installer.rb +13 -0
  212. data/lib/hecks/router.rb +55 -0
  213. data/lib/hecks/runtime/aggregate_lock.rb +9 -0
  214. data/lib/hecks/runtime/boot_gates.rb +18 -0
  215. data/lib/hecks/runtime/caller.rb +32 -0
  216. data/lib/hecks/runtime/capability_graph.rb +11 -0
  217. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
  218. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
  219. data/lib/hecks/runtime/command_interpreter.rb +42 -17
  220. data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
  221. data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
  222. data/lib/hecks/runtime/command_rules/references.rb +118 -28
  223. data/lib/hecks/runtime/dependency_planning.rb +45 -0
  224. data/lib/hecks/runtime/dispatcher.rb +28 -50
  225. data/lib/hecks/runtime/entity_element.rb +161 -8
  226. data/lib/hecks/runtime/entity_interpreter.rb +44 -9
  227. data/lib/hecks/runtime/errors.rb +18 -4
  228. data/lib/hecks/runtime/event.rb +10 -5
  229. data/lib/hecks/runtime/identity.rb +71 -3
  230. data/lib/hecks/runtime/instance.rb +67 -7
  231. data/lib/hecks/runtime/interpreting.rb +13 -5
  232. data/lib/hecks/runtime/invocation.rb +118 -36
  233. data/lib/hecks/runtime/loader.rb +94 -8
  234. data/lib/hecks/runtime/outbox.rb +145 -7
  235. data/lib/hecks/runtime/policy_interpreter.rb +22 -9
  236. data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
  237. data/lib/hecks/runtime/query_interpreter.rb +40 -12
  238. data/lib/hecks/runtime/reaction_invocation.rb +53 -8
  239. data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
  240. data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
  241. data/lib/hecks/runtime/reference_hop.rb +42 -0
  242. data/lib/hecks/runtime/refusal_wording.rb +50 -0
  243. data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
  244. data/lib/hecks/runtime/registry/verification.rb +119 -4
  245. data/lib/hecks/runtime/registry.rb +157 -4
  246. data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
  247. data/lib/hecks/runtime/routing.rb +27 -2
  248. data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
  249. data/lib/hecks/runtime/saga_interpreter.rb +27 -13
  250. data/lib/hecks/runtime/tenant_check.rb +26 -6
  251. data/lib/hecks/runtime/tenant_scope.rb +18 -0
  252. data/lib/hecks/runtime/value/coercion.rb +255 -33
  253. data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
  254. data/lib/hecks/runtime/value.rb +50 -0
  255. data/lib/hecks/runtime.rb +32 -0
  256. data/lib/hecks/storehouse.rb +305 -9
  257. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  258. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  259. data/lib/hecks/version.rb +1 -1
  260. data/lib/hecks.rb +79 -1
  261. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
  262. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
  263. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
  264. metadata +11 -5
  265. data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
  266. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
  267. 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
- # Renamed from `one_of` — item #13's full metaprogrammed dispatch
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
- # Renamed from `member` — item #13's full metaprogrammed dispatch
87
- # (slice 4c). Bootstrap-reachable, in
88
- # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
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
- # Renamed from `invariant` — item #13's full metaprogrammed
109
- # dispatch (slice 4b). Bootstrap-reachable, in
110
- # GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
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
- # ── AST → JSON — walks the real Evaluator/Resolver AST (the same
8
- # objects a live dispatch parses `given`/`ensures`/invariant text
9
- # into — see docs/implemented/guides/running-a-runtime.md's "The
10
- # expression grammar") and emits plain, JSON-serializable Ruby
11
- # Hashes, tagged by `"op"`.
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
- # Lives in core `lib/hecks`, not `rust/project/` — `rust/project.rb`
30
- # is a separate, downstream toolchain
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: every node this grammar admits gets
41
- # a real arm, the identical "raise, don't silently drop" discipline
42
- # `expr_emitter.rb`'s own `emit_bool`/`emit_resolver` already hold
43
- # to — even though, as of this writing, no real corpus value object
44
- # invariant exercises `Include`/`Modulo`/`BlockPredicate`/`Find`/
45
- # `Array`/`MatchesRegex`/`Presence`/`Assignment`/`Split`/`StartsWith`/
46
- # `EndsWith`/`First`/`Last` (only `given`/`ensures` clauses do, elsewhere in
47
- # the corpus — a different consumer of this same grammar).
48
- # `rust/host/src/expr_json.rs`'s own header names exactly which of
49
- # these its interpreter evaluates for real today versus refuses
50
- # cleanly — a narrower, deliberate, documented boundary on the
51
- # interpreting side, not on this emitting side: an author is free
52
- # to write any real expression in a value object's own `invariant`,
53
- # and this always emits it faithfully; whether `rust/host` can yet
54
- # check it at mint time is that file's own question to answer, not
55
- # this one's to pre-empt by refusing to even try.
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 a normalisation rule to the text outside quoted string
65
- # literals only, copying every quoted run through byte-for-byte.
66
- # Every rule here (collapse_whitespace, the `.length`→`.size` fold)
67
- # used to run quote-blind — `"a b"` collapsed to `"a b"` and
68
- # `"a.length"` folded to `"a.size"` just as readily as the real
69
- # source outside the quotes, silently rewriting what a predicate
70
- # compares a string attribute against, not merely how the
71
- # predicate itself is spelled. A canonical string literal's
72
- # contents are data, never syntax to normalise.
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 = +""