Skip to content

amuta/kumi

Repository files navigation

Kumi

CI Gem Version


Status: experimental. Public API may change. Typing and some static checks are still evolving.

Feedback: have a use case or a paper cut? Open an issue or reach out.


Kumi is a declarative calculation DSL for Ruby. You write business rules once; Kumi:

  • Builds a typed dependency graph with vector semantics.
  • Performs static validation at definition time.
  • Lowers to a portable Low-level IR (LIR).
  • Code-generates standalone Ruby and JavaScript.

Targets: finance, tax, pricing, insurance, payroll, analytics—domains where correctness, transparency, and reproducibility matter.


Codegen: Currently Ruby and JavaScript

Note: this is not available on the last published gem version (0.0.18), but if you clone this repository you can just copy a ./golden/$schema_name and modify schema.kumi as you and run bin/kumi golden test $schema_name

Kumi emits the kernel for your schema with no runtime in the target language. You can use it within your ruby application backend and/or export to the client.

Schema
module GameOfLife
  extend Kumi::Schema

  schema do
    input do
      array :rows do
        array :col do
          integer :alive # 0 or 1
        end
      end
    end

    let :a, input.rows.col.alive

    # axis_offset: 0 = x, 1 = y
    let :n,  shift(a, -1, axis_offset: 1)
    let :s,  shift(a,  1, axis_offset: 1)
    let :w,  shift(a, -1)
    let :e,  shift(a,  1)
    let :nw, shift(n, -1)
    let :ne, shift(n,  1)
    let :sw, shift(s, -1)
    let :se, shift(s,  1)

    let :neighbors, fn(:sum, [n, s, w, e, nw, ne, sw, se])

    # Conway rules
    let :alive, a > 0
    let :n3_alive, neighbors == 3
    let :n2_alive, neighbors == 2
    let :keep_alive, n2_alive & alive

    let :next_alive, n3_alive | keep_alive

    value :next_state, select(next_alive, 1, 0)
  end

end
Optimized LIR (lowered IR)
# ...
  (Declaration next_state
    %t285 = load_input "rows" :: array
    %t1539 = Length %t285 :: integer
    %t1540 = const -1 :: integer
    %t1542 = const 0 :: integer
    %t1546 = const 1 :: integer
    %t1334 = const 3 :: integer
    %t1339 = const 2 :: integer
    %t1547 = call core.sub(%t1539, %t1546) :: integer
    loop rows id=L31 in %t285 as el=%rows_el_286, idx=%rows_i_287
      %t1541 = call core.sub(%rows_i_287, %t1540) :: integer
      %t1561 = call core.sub(%rows_i_287, %t1546) :: integer
      %t1580 = call core.mod(%rows_i_287, %t1539) :: integer
      # ...
Generated Ruby (excerpt)
# Autogenerated by Kumi Codegen
module Kumi::Compiled::KUMI_bd17a3ebee1bec4e58b72118d43e8c1c93bf773f257fc93d9c32a783d212ea4f
  def self.from(input_data = nil)
    instance = Object.new
    instance.extend(self)
    instance.instance_variable_set(:@input, input_data)
    instance
  end

  def self.__kumi_executable__
    instance = Object.new
    instance.extend(self)
    instance
  end

  def update(input_data)
    @input = @input.merge(input_data)
    self
  end

  def [](name)
    case name
    when :next_state then _next_state
    else raise KeyError, "Unknown declaration"
    end
  end

  def _next_state(input = @input)
    out = []
    t285 = input["rows"] || input[:rows]
    t1539 = t285.length
    t1540 = -1
    t1542 = 0
    t1546 = 1
    t1334 = 3
    t1339 = 2
    t1547 = t1539 - t1546
    t285.each_with_index do |rows_el_286, rows_i_287|
      out_1 = []
      t1541 = rows_i_287 - t1540
      t1561 = rows_i_287 - t1546
      t1580 = rows_i_287 % t1539
      t1543 = t1541 >= t1542
      t1544 = t1541 < t1539
      t1549 = [[ t1541, t1542 ].max, t1547 ].min
      t1563 = t1561 >= t1542
# ...
Generated JavaScript (excerpt)
export class KumiCompiledModule {
  _next_state(input) {
    let out = [];
    let t285 = input["rows"];
    let t1539 = t285.length
    const t1540 = -1;
    const t1542 = 0;
    const t1546 = 1;
    const t1334 = 3;
    const t1339 = 2;
    let t1547 = t1539 - t1546;
    t285.forEach((rows_el_286, rows_i_287) => {
      let out_1 = [];
      let t1541 = rows_i_287 - t1540;
      let t1561 = rows_i_287 - t1546;
      let t1580 = ((rows_i_287 % t1539) + t1539) % t1539;
      let t1543 = t1541 >= t1542;
      let t1544 = t1541 < t1539;
      let t1549 = Math.min(Math.max(t1541, t1542), t1547);
      let t1563 = t1561 >= t1542;
      let t1564 = t1561 < t1539;
      let t1569 = Math.min(Math.max(t1561, t1542), t1547);
      let t1581 = t1580 + t1539;
      let t1545 = t1543 && t1544;
      let t1550 = t285[t1549]
      // ...

Install

gem install kumi

Requires Ruby 3.1+. No external dependencies.


License

MIT License. See LICENSE.