Module: Kumi::Dev::PrettyPrinter

Defined in:
lib/kumi/dev/pretty_printer.rb

Constant Summary collapse

IR_GRAPH_STAGES =

The IR-graph stages differ only in the pass to stop after and the state key to render — one row each instead of a copy-pasted method. :label is what the “Missing …” diagnostic prints.

{
  dfir: { stop_after: "DFValidatePass", state_key: :df_module_unoptimized, label: "DFIR" },
  dfir_optimized: { stop_after: "DFValidatePass", state_key: :df_module, label: "optimized DFIR" },
  vecir: { stop_after: "VecValidatePass", state_key: :vec_module, label: "VecIR" },
  loopir: { stop_after: "LoopValidatePass", state_key: :loop_module, label: "LoopIR" }
}.freeze
ANALYZED_STAGES =

Stages that run the full analyzer (side tables on) and render one state key. :render maps the state value to text.

{
  schema_ruby: { state_key: :ruby_codegen_files, render: ->(v) { v["codegen.rb"] } },
  schema_javascript: { state_key: :javascript_codegen_files, render: ->(v) { v["codegen.mjs"] } }
}.freeze

Class Method Summary collapse

Class Method Details

.analyze_schema(path, stop_after: nil, **opts) ⇒ Object



159
160
161
162
163
164
165
166
167
168
# File 'lib/kumi/dev/pretty_printer.rb', line 159

def analyze_schema(path, stop_after: nil, **opts)
  schema, = Kumi::Frontends.load(path: path)
  if stop_after
    with_stop_after(stop_after) do
      Kumi::Analyzer.analyze!(schema, **opts)
    end
  else
    Kumi::Analyzer.analyze!(schema, **opts)
  end
end

.format_node(name, node, indent) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/kumi/dev/pretty_printer.rb', line 57

def format_node(name, node, indent)
  prefix = "  " * indent
  result = []

  # Node header with type and container
  header = "#{prefix}#{name}: #{node.type}"
  header += " (#{node.container})" if node.container != :scalar
  header += " access_mode=#{node.access_mode}" if node.access_mode
  result << header

  # Child steps if any
  if node.child_steps && !node.child_steps.empty?
    node.child_steps.each do |child_name, steps|
      steps_str = steps.map { |s| s[:kind] }.join("")
      result << "#{prefix}  └─> #{child_name}: #{steps_str}"
    end
  end

  # Recursively print children
  if node.children && !node.children.empty?
    node.children.each do |child_name, child_node|
      result << format_node(child_name, child_node, indent + 1)
    end
  end

  result.join("\n")
end

.generate_analyzed(path, state_key:, render:) ⇒ Object



138
139
140
141
142
143
# File 'lib/kumi/dev/pretty_printer.rb', line 138

def generate_analyzed(path, state_key:, render:)
  schema, = Kumi::Frontends.load(path: path)
  res = Kumi::Analyzer.analyze!(schema, side_tables: true)
  value = res.state[state_key] or return nil
  render.call(value)
end

.generate_ast(path) ⇒ Object



37
38
39
40
# File 'lib/kumi/dev/pretty_printer.rb', line 37

def generate_ast(path)
  schema, = Kumi::Frontends.load(path: path)
  Kumi::Support::SExpressionPrinter.print(schema)
end

.generate_input_plan(path) ⇒ Object



42
43
44
45
46
47
# File 'lib/kumi/dev/pretty_printer.rb', line 42

def generate_input_plan(path)
  res = analyze_schema(path, stop_after: "InputAccessPlannerPass")
  return nil unless res.state[:input_metadata]

  print_input_plan(res.state[:input_metadata])
end

.generate_ir(path) ⇒ Object



85
86
87
88
89
90
91
# File 'lib/kumi/dev/pretty_printer.rb', line 85

def generate_ir(path)
  schema, = Kumi::Frontends.load(path: path)
  res = Kumi::Analyzer.analyze!(schema)
  return nil unless res.state[:ir_module]

  Kumi::Support::IRRender.to_text(res.state[:ir_module], analysis_state: res.state)
end

.generate_ir_graph(path, stop_after:, state_key:, label:) ⇒ Object



132
133
134
135
136
# File 'lib/kumi/dev/pretty_printer.rb', line 132

def generate_ir_graph(path, stop_after:, state_key:, label:)
  res = analyze_schema(path, stop_after: stop_after, side_tables: true)
  graph = res.state[state_key] or raise "Missing #{label} for #{path}"
  print_ir_graph(graph)
end

.generate_nast(path) ⇒ Object



93
94
95
96
97
# File 'lib/kumi/dev/pretty_printer.rb', line 93

def generate_nast(path)
  res = analyze_schema(path, stop_after: "NormalizeToNASTPass")
  mod = res.state[:nast_module] or raise "Missing NAST for #{path}"
  Kumi::Support::NASTPrinter.print(mod)
end

.generate_runtime(path) ⇒ Object

Executes the generated Ruby + JS against the golden’s input.json and snapshots the outputs (also asserting Ruby == JS). Returns nil when the golden has no input.json (text-only). See GoldenRuntime.



148
149
150
151
# File 'lib/kumi/dev/pretty_printer.rb', line 148

def generate_runtime(path)
  require_relative "golden_runtime"
  GoldenRuntime.snapshot(path)
end

.generate_snast(path) ⇒ Object



99
100
101
102
103
# File 'lib/kumi/dev/pretty_printer.rb', line 99

def generate_snast(path)
  res = analyze_schema(path, stop_after: "SNASTPass")
  mod = res.state[:snast_module] or raise "Missing SNAST for #{path}"
  Kumi::Support::SNASTPrinter.print(mod)
end


49
50
51
52
53
54
55
# File 'lib/kumi/dev/pretty_printer.rb', line 49

def print_input_plan(, indent = 0)
  lines = []
  .each do |name, node|
    lines << format_node(name, node, indent)
  end
  lines.join("\n")
end


153
154
155
156
157
# File 'lib/kumi/dev/pretty_printer.rb', line 153

def print_ir_graph(graph)
  io = StringIO.new
  Kumi::IR::Printer.print(graph, io: io)
  io.string
end

.run(kind, path) ⇒ Object



11
12
13
14
15
16
17
# File 'lib/kumi/dev/pretty_printer.rb', line 11

def run(kind, path)
  method_name = "generate_#{kind}"
  raise "Unknown pretty print kind: #{kind}" unless respond_to?(method_name)

  output = send(method_name, path)
  puts output if output
end

.with_stop_after(pass_name) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/kumi/dev/pretty_printer.rb', line 19

def with_stop_after(pass_name)
  saved = {
    "KUMI_STOP_AFTER" => ENV.fetch("KUMI_STOP_AFTER", nil),
    "KUMI_CHECKPOINT" => ENV.fetch("KUMI_CHECKPOINT", nil),
    "KUMI_RESUME_FROM" => ENV.fetch("KUMI_RESUME_FROM", nil),
    "KUMI_RESUME_AT" => ENV.fetch("KUMI_RESUME_AT", nil)
  }

  ENV["KUMI_STOP_AFTER"] = pass_name
  ENV["KUMI_CHECKPOINT"] = "0"
  ENV.delete("KUMI_RESUME_FROM")
  ENV.delete("KUMI_RESUME_AT")

  yield
ensure
  saved.each { |k, v| v ? ENV[k] = v : ENV.delete(k) }
end