hecks 3.2.0 → 3.3.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/bluebook/assembly/contracts.rb +2 -1
  3. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  4. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
  5. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
  6. data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
  7. data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
  8. data/lib/hecks/bluebook/query.rb +8 -5
  9. data/lib/hecks/cli/check_era.rb +3 -39
  10. data/lib/hecks/cli/interview_draft.rb +86 -31
  11. data/lib/hecks/cli/interview_session.rb +77 -26
  12. data/lib/hecks/cli/interview_shape.rb +179 -0
  13. data/lib/hecks/doors/cli_runner.rb +14 -1
  14. data/lib/hecks/doors/launcher_options.rb +13 -3
  15. data/lib/hecks/doors/mcp_door.rb +16 -2
  16. data/lib/hecks/doors/mcp_door_scope.rb +45 -5
  17. data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
  18. data/lib/hecks/doors.rb +1 -1
  19. data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
  20. data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
  21. data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
  22. data/lib/hecks/embryonaut_bluebook.rb +2 -0
  23. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
  24. data/lib/hecks/fuzzing/qa_settings.rb +15 -34
  25. data/lib/hecks/gate/stages.yml +113 -8
  26. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
  27. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
  28. data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
  29. data/lib/hecks/hecks/adapters/git.rb +59 -0
  30. data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
  31. data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
  32. data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
  33. data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
  34. data/lib/hecks/hecks/adapters/site_toolchain.rb +88 -11
  35. data/lib/hecks/hecks/codebase.behaviors +6 -0
  36. data/lib/hecks/hecks/codebase.bluebook +21 -0
  37. data/lib/hecks/hecks/custodian.behaviors +6 -0
  38. data/lib/hecks/hecks/custodian.bluebook +217 -0
  39. data/lib/hecks/hecks/hecks.hecksagon +24 -0
  40. data/lib/hecks/hecks/hecks.world +8 -3
  41. data/lib/hecks/hecks/site.world +5 -2
  42. data/lib/hecks/language/bluebook/query.bluebook +25 -0
  43. data/lib/hecks/language/bluebook/vocabulary.bluebook +220 -7
  44. data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
  45. data/lib/hecks/language/oidc.json +5 -0
  46. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
  47. data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
  48. data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
  49. data/lib/hecks/projections/deploy/box/settings.rb +40 -6
  50. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +2 -0
  51. data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
  52. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
  53. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
  54. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
  55. data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
  56. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
  57. data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
  58. data/lib/hecks/projections/deploy/box.rb +28 -4
  59. data/lib/hecks/projections/deploy/fargate.rb +3 -3
  60. data/lib/hecks/projections/deploy/lambda.rb +8 -8
  61. data/lib/hecks/projections/model/deviations.rb +1 -1
  62. data/lib/hecks/projections/site/edge/behaviours.rb +5 -3
  63. data/lib/hecks/projections/site/edge/checks.rb +31 -5
  64. data/lib/hecks/projections/site/edge.rb +25 -11
  65. data/lib/hecks/projections/site/live_edge.rb +162 -0
  66. data/lib/hecks/projections/site/routes_ts.rb +50 -27
  67. data/lib/hecks/projections/site/site_cdn.rb +7 -4
  68. data/lib/hecks/projections/site/table/checks.rb +53 -7
  69. data/lib/hecks/projections/site/table/link_builder.rb +64 -0
  70. data/lib/hecks/projections/site/table/row_builder.rb +21 -6
  71. data/lib/hecks/projections/site/table.rb +16 -5
  72. data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
  73. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
  74. data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
  75. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
  76. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
  77. data/lib/hecks/quality_control/quality_control.bluebook +76 -124
  78. data/lib/hecks/runtime/interpreting.rb +3 -22
  79. data/lib/hecks/runtime/needs.rb +48 -0
  80. data/lib/hecks/runtime/query_interpreter.rb +3 -0
  81. data/lib/hecks/runtime/registry/verification.rb +3 -3
  82. data/lib/hecks/rust_build/host.rb +147 -0
  83. data/lib/hecks/rust_build/kernel_input.rb +51 -17
  84. data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
  85. data/lib/hecks/rust_build/project_rust.rb +7 -140
  86. data/lib/hecks/rust_build.rb +1 -0
  87. data/lib/hecks/site/bluebook/site.bluebook +80 -14
  88. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  89. data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
  90. data/lib/hecks/storehouse.rb +35 -1
  91. data/lib/hecks/tools/comment_style.rb +4 -2
  92. data/lib/hecks/tools/gate.rb +1 -1
  93. data/lib/hecks/tools/rust_comment_style.rb +2 -4
  94. data/lib/hecks/tools/site_routes.rb +69 -28
  95. data/lib/hecks/tools/tools_doc.rb +204 -0
  96. data/lib/hecks/tools.rb +1 -0
  97. data/lib/hecks/version.rb +1 -1
  98. data/lib/hecks/vocabulary.rb +152 -1
  99. data/lib/hecks.rb +1 -0
  100. data/qa/settings.yml +1 -2
  101. data/rust/build/src/lineage_pass.rs +31 -16
  102. data/rust/build/src/main.rs +4 -2
  103. data/rust/build/src/pipeline.rs +9 -20
  104. data/rust/codegen/src/attr.rs +1 -1
  105. data/rust/codegen/src/bridging.rs +8 -8
  106. data/rust/codegen/src/commands.rs +1 -1
  107. data/rust/codegen/src/constraints.rs +1 -1
  108. data/rust/codegen/src/dependency_planning.rs +1 -1
  109. data/rust/codegen/src/domain_generator.rs +2 -2
  110. data/rust/codegen/src/exemplar.rs +1 -1
  111. data/rust/codegen/src/expr_emitter.rs +1 -1
  112. data/rust/codegen/src/fielded.rs +1 -1
  113. data/rust/codegen/src/json.rs +214 -23
  114. data/rust/codegen/src/json_codec.rs +1 -1
  115. data/rust/codegen/src/literal.rs +6 -3
  116. data/rust/codegen/src/main.rs +23 -13
  117. data/rust/codegen/src/mutations.rs +1 -1
  118. data/rust/codegen/src/naming.rs +5 -3
  119. data/rust/codegen/src/optional_pass.rs +226 -0
  120. data/rust/codegen/src/ports.rs +1 -1
  121. data/rust/codegen/src/queries.rs +3 -3
  122. data/rust/codegen/src/reactions.rs +1 -1
  123. data/rust/codegen/src/read_models.rs +2 -2
  124. data/rust/codegen/src/reference_specs.rs +1 -1
  125. data/rust/codegen/src/registry.rs +4 -4
  126. data/rust/codegen/src/shared.rs +1 -1
  127. data/rust/{build → codegen}/src/sidecars.rs +9 -5
  128. data/rust/codegen/src/skip_reason.rs +1 -1
  129. data/rust/codegen/src/types.rs +4 -4
  130. data/rust/host/HECKS_RELEASE +1 -1
  131. data/rust/host/src/api.rs +1 -1
  132. data/rust/host/src/dispatch.rs +5 -0
  133. data/rust/host/src/needs.rs +193 -29
  134. data/rust/host/src/resend.rs +4 -4
  135. data/rust/host/src/ui_schema.rs +1 -1
  136. data/rust/parser/src/emit.rs +12 -0
  137. data/rust/parser/src/ir.rs +2 -0
  138. data/rust/parser/src/keywords.rs +2 -0
  139. data/rust/parser/src/main.rs +1 -0
  140. data/rust/parser/src/parse/command.rs +2 -44
  141. data/rust/parser/src/parse/mod.rs +1 -0
  142. data/rust/parser/src/parse/needs.rs +60 -0
  143. data/rust/parser/src/parse/query.rs +3 -0
  144. data/rust/src/exemplar/commands.rs +1 -1
  145. data/rust/src/exemplar/constraints.rs +1 -1
  146. data/rust/src/exemplar/fielded.rs +1 -1
  147. data/rust/src/exemplar/json.rs +1 -1
  148. data/rust/src/exemplar/mod.rs +1 -1
  149. data/rust/src/exemplar/mutations.rs +1 -1
  150. data/rust/src/exemplar/queries.rs +1 -1
  151. data/rust/src/exemplar/reactions.rs +1 -1
  152. data/rust/src/exemplar/read_models.rs +1 -1
  153. data/rust/src/exemplar/registry.rs +1 -1
  154. data/rust/src/exemplar/types.rs +1 -1
  155. data/rust/src/kernel/cli.rs +18 -8
  156. data/rust/src/kernel/needs.rs +104 -9
  157. data/rust/src/kernel/read_model.rs +1 -1
  158. data/rust/src/lib.rs +1 -1
  159. metadata +24 -30
  160. data/lib/hecks/rust_build/append_optionals.rb +0 -79
  161. data/lib/hecks/rust_build/rust_literal.rb +0 -33
  162. data/lib/hecks/three_zero/forms.yml +0 -127
  163. data/lib/hecks/three_zero.rb +0 -12
  164. data/rust/build/src/json.rs +0 -421
  165. data/rust/build/src/optional_pass.rs +0 -150
  166. data/rust/project/bridging.rb +0 -257
  167. data/rust/project/commands.rb +0 -877
  168. data/rust/project/constraints.rb +0 -86
  169. data/rust/project/dependency_planning.rb +0 -178
  170. data/rust/project/domain_generator.rb +0 -983
  171. data/rust/project/exemplar.rb +0 -148
  172. data/rust/project/expr_emitter.rb +0 -72
  173. data/rust/project/fielded.rb +0 -141
  174. data/rust/project/json_codec.rb +0 -641
  175. data/rust/project/mutations.rb +0 -695
  176. data/rust/project/naming.rb +0 -223
  177. data/rust/project/ports.rb +0 -65
  178. data/rust/project/queries.rb +0 -493
  179. data/rust/project/reactions.rb +0 -317
  180. data/rust/project/read_models.rb +0 -690
  181. data/rust/project/reference_specs.rb +0 -27
  182. data/rust/project/registry.rb +0 -396
  183. data/rust/project/skip_reason.rb +0 -29
  184. data/rust/project/types.rb +0 -225
  185. data/rust/project/write_if_changed.rb +0 -7
  186. data/rust/project.rb +0 -30
  187. data/rust/project_rust_pipeline.rb +0 -391
@@ -1,148 +0,0 @@
1
- module RustProjection
2
- # Loads the `// TMPL:<id> BEGIN/END` shapes in rust/src/exemplar/*.rs and renders them
3
- # by substituting the fixed `Tmpl`/`tmpl_`/`TMPL_` placeholders with real domain names.
4
- module Exemplar
5
- # Matches any placeholder token. After substitution nothing may match, so an
6
- # unfilled placeholder raises instead of leaking `TmplType` into generated output.
7
- LEFTOVER_PLACEHOLDER = /\b(?:Tmpl\w*|tmpl_\w*|TMPL_\w*)\b/
8
-
9
- DEFAULT_DIR = File.expand_path("../src/exemplar", __dir__)
10
-
11
- class DriftError < StandardError; end
12
-
13
- class << self
14
- # Overridable so specs can point at a scratch fixture tree.
15
- def dir
16
- @dir ||= DEFAULT_DIR
17
- end
18
-
19
- # `id` -> raw shape text (dedented, marker lines excluded).
20
- # A nested region leaves a `<<TMPL_SLOT:<id>:<sub>>>` sentinel; see `compose`.
21
- def shapes
22
- @shapes ||= parse_all
23
- end
24
-
25
- # Renders `shape_id`, replacing each `subs` key (a literal substring) with its value.
26
- # Raises DriftError on a key absent from the shape or a placeholder left over.
27
- #
28
- # Uses the block form of gsub: the two-arg form interprets backslash sequences in
29
- # the replacement, which corrupts escaped regex sources.
30
- def render(shape_id, subs)
31
- text = raw(shape_id)
32
-
33
- subs.keys.sort_by { |k| -k.length }.each do |marker|
34
- raise DriftError, "Exemplar.render(#{shape_id.inspect}): marker #{marker.inspect} not found in shape text — " \
35
- "exemplar and Ruby caller have drifted" unless text.include?(marker)
36
-
37
- value = subs[marker]
38
- text = text.gsub(marker) { value }
39
- end
40
-
41
- if (leftover = text.scan(LEFTOVER_PLACEHOLDER)).any?
42
- raise DriftError, "Exemplar.render(#{shape_id.inspect}): unrendered placeholder(s) #{leftover.uniq.inspect} — " \
43
- "subs is missing a marker this shape actually contains"
44
- end
45
-
46
- text
47
- end
48
-
49
- # Renders `shape_id` once per entry in `subs_list`, joined by `join_with`.
50
- def render_each(shape_id, subs_list, join_with: "\n")
51
- subs_list.map { |subs| render(shape_id, subs) }.join(join_with)
52
- end
53
-
54
- # Fills every nested slot of `outer_id` (`slots` maps field_id => rendered content),
55
- # reindenting each to its marker's depth, then applies `outer_subs` last so a marker
56
- # cannot match text that came from a slot.
57
- def assemble(outer_id, outer_subs, slots:)
58
- outer_text = raw(outer_id)
59
-
60
- slots.each do |field_id, content|
61
- slot = "<<TMPL_SLOT:#{field_id}>>"
62
- slot_line = outer_text.match(/^([ \t]*)#{Regexp.escape(slot)}$/)
63
- raise DriftError, "Exemplar.assemble(#{outer_id.inspect}): no nested slot for #{field_id.inspect} — " \
64
- "expected a `// TMPL:#{field_id} BEGIN/END` region nested inside `// TMPL:#{outer_id} BEGIN/END`" unless slot_line
65
-
66
- # The nested marker's indentation in the exemplar sets the depth, not the caller.
67
- indent = slot_line[1]
68
- reindented = content.lines.map { |l| l.strip.empty? ? l : "#{indent}#{l}" }.join
69
- outer_text = outer_text.sub(slot_line[0]) { reindented } # block form: see render
70
- end
71
-
72
- outer_subs.keys.sort_by { |k| -k.length }.each do |marker|
73
- raise DriftError, "Exemplar.assemble(#{outer_id.inspect}): marker #{marker.inspect} not found in outer shape text" unless outer_text.include?(marker)
74
-
75
- value = outer_subs[marker]
76
- outer_text = outer_text.gsub(marker) { value }
77
- end
78
-
79
- if (leftover = outer_text.scan(LEFTOVER_PLACEHOLDER)).any?
80
- raise DriftError, "Exemplar.assemble(#{outer_id.inspect}): unrendered placeholder(s) #{leftover.uniq.inspect}"
81
- end
82
-
83
- outer_text
84
- end
85
-
86
- # `assemble` for the single-slot case: renders the inner shape `field_id` once per
87
- # entry in `field_subs_list` and splices the joined result into `outer_id`.
88
- def compose(outer_id, outer_subs, field_id:, field_subs_list:, join_with: "\n")
89
- assemble(outer_id, outer_subs, slots: { field_id => render_each(field_id, field_subs_list, join_with: join_with) })
90
- end
91
-
92
- def raw(shape_id)
93
- shapes.fetch(shape_id) do
94
- raise DriftError, "Exemplar: no shape #{shape_id.inspect} — " \
95
- "checked rust/src/exemplar/*.rs for `// TMPL:#{shape_id} BEGIN`"
96
- end
97
- end
98
-
99
- # Test-only: repoints `dir` and clears the memoized shapes.
100
- def reset!(dir: DEFAULT_DIR)
101
- @dir = dir
102
- @shapes = nil
103
- end
104
-
105
- private
106
-
107
- def parse_all
108
- result = {}
109
- Dir.glob(File.join(dir, "*.rs")).sort.each { |path| parse_file(path, result) }
110
- result
111
- end
112
-
113
- def parse_file(path, result)
114
- stack = [] # each frame: [id, lines, begin_indent]
115
- File.readlines(path).each do |line|
116
- if (id = begin_marker(line))
117
- stack.push([id, [], line[/^[ \t]*/]])
118
- elsif (id = end_marker(line))
119
- raise "#{path}: TMPL:#{id} END with no matching BEGIN (stack: #{stack.map(&:first).inspect})" if stack.empty? || stack.last[0] != id
120
-
121
- _, lines, begin_indent = stack.pop
122
- result[id] = dedent(lines)
123
- # Keep the nested marker's indentation; it sets the depth of the spliced lines.
124
- stack.last[1] << "#{begin_indent}<<TMPL_SLOT:#{id}>>\n" if stack.any?
125
- elsif stack.any?
126
- stack.last[1] << line
127
- end
128
- end
129
- raise "#{path}: unclosed TMPL region(s): #{stack.map(&:first).inspect}" if stack.any?
130
- end
131
-
132
- def begin_marker(line)
133
- line[%r{//\s*TMPL:(\S+)\s+BEGIN\s*$}, 1]
134
- end
135
-
136
- def end_marker(line)
137
- line[%r{//\s*TMPL:(\S+)\s+END\s*$}, 1]
138
- end
139
-
140
- # Drops the leading whitespace shared by all non-blank lines, like `<<~`.
141
- def dedent(lines)
142
- indents = lines.reject { |l| l.strip.empty? }.map { |l| l[/^[ \t]*/].length }
143
- margin = indents.min || 0
144
- lines.map { |l| l.empty? ? l : l[margin..] }.join.rstrip
145
- end
146
- end
147
- end
148
- end
@@ -1,72 +0,0 @@
1
- module RustProjection
2
- # Emits Rust `Expr` data-literal source from the `ast` tree of an IR rule row.
3
- # Mirrors rust/codegen/src/expr_emitter.rs node for node; see `AstJson::OPS`.
4
- # `include` arm below only ever sees a real field/string haystack.
5
- module ExprEmitter
6
- module_function
7
-
8
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
9
- def emit_ast(node)
10
- case at(node, :op)
11
- when "or" then "Expr::Or(Box::new(#{emit_ast(at(node, :left))}), Box::new(#{emit_ast(at(node, :right))}))"
12
- when "and" then "Expr::And(Box::new(#{emit_ast(at(node, :left))}), Box::new(#{emit_ast(at(node, :right))}))"
13
- when "not" then "Expr::Not(Box::new(#{emit_ast(at(node, :expr))}))"
14
- when "compare"
15
- "Expr::Compare { op: #{emit_comparison(at(node, :cmp))}, left: Box::new(#{emit_ast(at(node, :left))}), right: Box::new(#{emit_ast(at(node, :right))}) }"
16
- when "include"
17
- "Expr::Include { haystack: Box::new(#{emit_ast(at(node, :haystack))}), needle: Box::new(#{emit_ast(at(node, :needle))}) }"
18
- when "int" then "Expr::Int(#{at(node, :value)})"
19
- when "float" then "Expr::Float(#{at(node, :value)}f64)"
20
- when "str" then "Expr::Str(#{at(node, :value).inspect}.to_string())"
21
- when "bool" then "Expr::Bool(#{at(node, :value)})"
22
- when "nil" then "Expr::Nil"
23
- when "lookup" then "Expr::Lookup(#{at(node, :path).join('.').inspect})"
24
- when "add" then "Expr::Add(Box::new(#{emit_ast(at(node, :left))}), Box::new(#{emit_ast(at(node, :right))}))"
25
- when "sign_test"
26
- "Expr::SignTest { op: #{emit_comparison(at(node, :cmp))}, receiver: Box::new(#{emit_ast(at(node, :receiver))}) }"
27
- when "empty" then "Expr::Empty(Box::new(#{emit_ast(at(node, :receiver))}))"
28
- when "to_s" then "Expr::ToS(Box::new(#{emit_ast(at(node, :receiver))}))"
29
- when "modulo"
30
- "Expr::Modulo { receiver: Box::new(#{emit_ast(at(node, :receiver))}), divisor: Box::new(#{emit_ast(at(node, :divisor))}) }"
31
- when "size" then "Expr::Size(Box::new(#{emit_ast(at(node, :receiver))}))"
32
- when "block_predicate"
33
- "Expr::BlockPredicate { mode: crate::kernel::BlockMode::#{at(node, :mode).capitalize}, receiver: Box::new(#{emit_ast(at(node, :receiver))}), " \
34
- "param: #{at(node, :param).inspect}, predicate: Box::new(#{emit_ast(at(node, :predicate))}) }"
35
- when "find"
36
- "Expr::Find { receiver: Box::new(#{emit_ast(at(node, :receiver))}), param: #{at(node, :param).inspect}, " \
37
- "predicate: Box::new(#{emit_ast(at(node, :predicate))}), path: &[#{at(node, :path).map(&:inspect).join(', ')}] }"
38
- when "array"
39
- "Expr::Array(vec![#{at(node, :elements).map { |element| emit_ast(element) }.join(', ')}])"
40
- when "matches_regex"
41
- "Expr::MatchesRegex { receiver: Box::new(#{emit_ast(at(node, :receiver))}), pattern: #{at(node, :pattern).inspect}.to_string(), flags: #{at(node, :flags).inspect}.to_string() }"
42
- when "presence"
43
- "Expr::Presence { receiver: Box::new(#{emit_ast(at(node, :receiver))}), negated: #{at(node, :negated)} }"
44
- when "assignment"
45
- "Expr::Assignment { receiver: Box::new(#{emit_ast(at(node, :receiver))}), negated: #{at(node, :negated)} }"
46
- when "split"
47
- "Expr::Split { receiver: Box::new(#{emit_ast(at(node, :receiver))}), separator: #{at(node, :separator).inspect}.to_string() }"
48
- when "starts_with"
49
- "Expr::StartsWith { receiver: Box::new(#{emit_ast(at(node, :receiver))}), substring: #{at(node, :substring).inspect}.to_string() }"
50
- when "ends_with"
51
- "Expr::EndsWith { receiver: Box::new(#{emit_ast(at(node, :receiver))}), substring: #{at(node, :substring).inspect}.to_string() }"
52
- when "first" then "Expr::First(Box::new(#{emit_ast(at(node, :receiver))}))"
53
- when "last" then "Expr::Last(Box::new(#{emit_ast(at(node, :receiver))}))"
54
- else
55
- # An op with no arm above means `AstJson::OPS` grew and this file did not.
56
- raise "unhandled ast op #{at(node, :op).inspect} — no Rust rendering exists for it in this generator (rust/project/expr_emitter.rb#emit_ast)"
57
- end
58
- end
59
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
60
-
61
- # Fully qualified: the grammar's own "Comparison" closed set would collide with the
62
- # kernel struct of the same name in a file that needs both.
63
- def emit_comparison(cmp)
64
- "crate::kernel::Comparison { less_than: #{at(cmp, :less_than)}, equal: #{at(cmp, :equal)}, negated: #{at(cmp, :negated)} }"
65
- end
66
-
67
- # Reads a key from a symbol-keyed (round-tripped) or string-keyed (`AstJson`) node.
68
- def at(node, key)
69
- node.key?(key) ? node[key] : node.fetch(key.to_s)
70
- end
71
- end
72
- end
@@ -1,141 +0,0 @@
1
- module RustProjection
2
- module Projector
3
- module_function
4
-
5
- # One `Fielded` match arm per attribute, shared by value objects, args structs and
6
- # (via emit_fielded_record) aggregate records.
7
- # `extra_arms:` are raw `"key" => ...,` lines appended verbatim, e.g. an entity's lifecycle
8
- # field. Arms render from the leaf shapes in rust/src/exemplar/fielded.rs with a fixed
9
- # 12-space prefix, using plain `render` because the leaves are shared across outer shapes.
10
- def emit_fielded_flat(struct_name, attributes, value_objects_by_name, extra_arms: [], entity_names: [])
11
- arms = attributes.filter_map do |attr|
12
- key = rust_field(attr[:name])
13
- ident = rust_ident_field(attr[:name])
14
- scalar = effective_scalar_type(attr[:type])
15
- if attr[:list] && attr[:optional]
16
- Exemplar.render("fielded_arm_list_optional", '"tmpl_field"' => key.inspect, "tmpl_ident" => ident)
17
- elsif attr[:list]
18
- Exemplar.render("fielded_arm_list", '"tmpl_field"' => key.inspect, "tmpl_ident" => ident)
19
- elsif attr[:optional] && scalar
20
- Exemplar.render(
21
- "fielded_arm_optional_scalar",
22
- '"tmpl_field"' => key.inspect,
23
- "tmpl_ident" => ident,
24
- "tmpl_value_expr_placeholder(v)" => scalar_to_value(scalar, "v")
25
- )
26
- elsif attr[:optional]
27
- nested = value_objects_by_name[attr[:type]]
28
- next nil unless nested && fielded_capable_nested?(nested)
29
-
30
- Exemplar.render("fielded_arm_optional_nested", '"tmpl_field"' => key.inspect, "tmpl_ident" => ident)
31
- elsif scalar
32
- Exemplar.render(
33
- "fielded_arm_scalar",
34
- '"tmpl_field"' => key.inspect,
35
- "tmpl_value_expr_placeholder(&self.tmpl_ident)" => scalar_to_value(scalar, "self.#{ident}")
36
- )
37
- else
38
- nested = value_objects_by_name[attr[:type]]
39
- next nil unless nested && fielded_capable_nested?(nested)
40
-
41
- Exemplar.render("fielded_arm_nested", '"tmpl_field"' => key.inspect, "tmpl_ident" => ident)
42
- end
43
- end
44
- arms = arms.map { |a| " #{a}" }
45
- arms += extra_arms
46
-
47
- # The trailing "\n" keeps the blank-line count callers expect; render's dedent rstrips.
48
- "#{Exemplar.render(
49
- 'fielded_flat',
50
- 'TmplFlatType' => struct_name,
51
- '"tmpl_arms_placeholder" => tmpl_arms_block(),' => arms.join("\n"),
52
- # `entity_names`: a list of entities is enumerated the same way the record does.
53
- '"tmpl_items_placeholder" => tmpl_items_block(),' => items_arms(attributes, value_objects_by_name, entity_names, optional: ->(attr) { attr[:optional] }).join("\n"),
54
- "tmpl_as_scalar_placeholder()" => as_scalar_expr(attributes),
55
- # Import only what the arms use, so a struct with no arms leaves no unused `use`.
56
- 'use crate::kernel::Field;' => (arms.any? { |arm| arm.include?('Field') } ? 'use crate::kernel::Field;' : ''),
57
- 'use crate::kernel::Value;' => (arms.any? { |arm| arm.include?('Value') } ? 'use crate::kernel::Value;' : '')
58
- )}\n"
59
- end
60
-
61
- # `Fielded::items`: one arm per list attribute whose element is a scalar or a fielded-capable
62
- # nested type or entity. `optional` says whether the list is `Option`-wrapped.
63
- def items_arms(attributes, value_objects_by_name, entity_names, optional:)
64
- attributes.filter_map do |attr|
65
- next nil unless attr[:list]
66
-
67
- key = rust_field(attr[:name])
68
- ident = rust_ident_field(attr[:name])
69
- scalar = effective_scalar_type(attr[:type])
70
- nested = value_objects_by_name[attr[:type]]
71
- fielded_element = scalar || (nested && fielded_capable_nested?(nested)) || entity_names.include?(attr[:type])
72
- next nil unless fielded_element
73
-
74
- shape = "fielded_items_arm_list_#{optional.call(attr) ? 'optional_' : ''}#{scalar ? 'scalar' : 'nested'}"
75
- subs = { '"tmpl_field"' => key.inspect, "tmpl_ident" => ident }
76
- subs["tmpl_value_expr_placeholder(v)"] = scalar_to_value(scalar, "v") if scalar
77
- " #{Exemplar.render(shape, subs)}"
78
- end
79
- end
80
-
81
- # Record variant of emit_fielded_flat: non-list attributes are `Option`-wrapped, so `None`
82
- # reads as `Value::Nil`. Adds lifecycle and corrects-flag arms.
83
- def emit_fielded_record(aggregate, value_objects_by_name)
84
- name = rust_ident(aggregate[:name])
85
- arms = aggregate[:attributes].filter_map do |attr|
86
- key = rust_field(attr[:name])
87
- ident = rust_ident_field(attr[:name])
88
- scalar = effective_scalar_type(attr[:type])
89
- if attr[:list] && list_attr_creation_optional?(aggregate, attr[:name], value_objects_by_name)
90
- Exemplar.render("fielded_arm_list_optional", '"tmpl_field"' => key.inspect, "tmpl_ident" => ident)
91
- elsif attr[:list]
92
- Exemplar.render("fielded_arm_list", '"tmpl_field"' => key.inspect, "tmpl_ident" => ident)
93
- elsif scalar
94
- Exemplar.render(
95
- "fielded_arm_optional_scalar",
96
- '"tmpl_field"' => key.inspect,
97
- "tmpl_ident" => ident,
98
- "tmpl_value_expr_placeholder(v)" => scalar_to_value(scalar, "v")
99
- )
100
- else
101
- nested = value_objects_by_name[attr[:type]]
102
- next nil unless nested && fielded_capable_nested?(nested)
103
-
104
- Exemplar.render("fielded_arm_optional_nested", '"tmpl_field"' => key.inspect, "tmpl_ident" => ident)
105
- end
106
- end
107
- if aggregate[:lifecycle]
108
- key = rust_field(aggregate[:lifecycle][:field])
109
- ident = rust_ident_field(aggregate[:lifecycle][:field])
110
- arms << Exemplar.render("fielded_lifecycle_arm", '"tmpl_field"' => key.inspect, "tmpl_ident" => ident)
111
- end
112
- correctable_event_names(aggregate).each do |ev|
113
- ident = corrects_flag_field(ev)
114
- arms << Exemplar.render("fielded_corrects_flag_arm", '"tmpl_field"' => ident.inspect, "tmpl_ident" => ident)
115
- end
116
- arms = arms.map { |a| " #{a}" }
117
-
118
- # Trailing "\n": see emit_fielded_flat.
119
- entity_names = (aggregate[:entities] || []).map { |e| e[:name] }
120
- "#{Exemplar.render(
121
- 'fielded_record',
122
- 'TmplRecordType' => name,
123
- '"tmpl_arms_placeholder" => tmpl_arms_block(),' => arms.join("\n"),
124
- '"tmpl_items_placeholder" => tmpl_items_block(),' => items_arms(
125
- aggregate[:attributes], value_objects_by_name, entity_names,
126
- optional: ->(attr) { list_attr_creation_optional?(aggregate, attr[:name], value_objects_by_name) }
127
- ).join("\n"),
128
- "tmpl_as_scalar_placeholder()" => as_scalar_expr(aggregate[:attributes])
129
- )}\n"
130
- end
131
-
132
- # `Resolver#unwrap_scalar`: a struct with exactly one scalar attribute reads as that
133
- # attribute's value whatever its name; anything else answers `None`.
134
- def as_scalar_expr(attributes)
135
- sole = attributes.size == 1 && !attributes.first[:list] ? attributes.first : nil
136
- return "None" unless sole && effective_scalar_type(sole[:type])
137
-
138
- "match self.field(#{rust_field(sole[:name]).inspect}) { Some(crate::kernel::Field::Value(v)) => Some(v), _ => None }"
139
- end
140
- end
141
- end